物联网工程师技术之C语言编程基础

举报
tea_year 发表于 2024/01/18 18:07:42 2024/01/18
【摘要】 第2章C语言编程基础本章重点C语言基本数据类型常量变量类型转换在开发C语言程序时,需要掌握一些基本语法,如变量的定义、常量的定义、类型转换等,本章将针对这些知识进行详细地讲解。认识二进制二进制是计算技术中广泛采用的一种数制。二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”,由18世纪德国数理哲学大师莱布尼兹发现。当前的计算机系统使用的基本上...

第2章C语言编程基础

本章重点

  • C语言基本数据类型
  • 常量
  • 变量
  • 类型转换

在开发C语言程序时,需要掌握一些基本语法,如变量的定义、常量的定义、类型转换等,本章将针对这些知识进行详细地讲解。

  • 认识二进制

二进制是计算技术中广泛采用的一种数制二进制数据是用0和1两个数码来表示的数。它的基数为2,进位规则是“逢二进一”,借位规则是“借一当二”,由18世纪德国数理哲学大师莱布尼兹发现。当前的计算机系统使用的基本上是二进制系统,数据在计算机中主要是以补码的形式存储的。计算机中的二进制则是一个非常微小的开关,用“开”来表示1,“关”来表示0

1. 十进制与二进制之间的转换

一个十进制数1024对应的二进制表示为100 0000 0000(为便于阅读,在每四位数之间以一个空格进行分隔)。该如何转换呢?

首先简单4二进制数为例:假设4bit,每一个bit的值01这样一共可以表示24=16不同的二进制数:

21 4位二进制数

二进制

十进制

二进制

十进制

二进制

十进制

二进制

十进制

0000

 

0100

 

1000

 

1100

 

0001

 

0101

 

1001

 

1101

 

0010

 

0110

 

1010

 

1110

 

0011

 

0111

 

1011

 

1111

 

4位二进制数从右到左分别称为03位。根据之前介绍二进制表示规则,第01表示120=111表示121=221表示122=431表示123=8据此可以写出16二进制数对应的十进制值。以二进制的1101为例,它对应的十进制整数是8+4+1=13,如图2-1所示:


21 二进制到十进制的转换示意

下表为4位二进制数到十进制数的转换表。

22 4位二进制数和十进制数

二进制

十进制

二进制

十进制

二进制

十进制

二进制

十进制

0000

0

0100

4

1000

8

1100

12

0001

1

0101

5

1001

9

1101

13

0010

2

0110

6

1010

10

1110

14

0011

3

0111

7

1011

11

1111

15

&多学一招:负整数的补码表示法

大家已经知道了如何用二进制表示一个正整数,那么二进制又是如何表示负整数的呢?一个自然想法是利用最高位来表示符号位,然后利用剩下的bit来表示绝对值4二进制数为例:如果规定最高位用0表示正整数,用1表示负整数的话,164二进制数的值分别是:

23 4位二进制数和十进制数

二进制

十进制

二进制

十进制

二进制

十进制

二进制

十进制

0000

0

0100

4

1000

-0

1100

-4

0001

1

0101

5

1001

-1

1101

-5

0010

2

0110

6

1010

-2

1110

-6

0011

3

0111

7

1011

-3

1111

-7

很可惜这样的表示方法有几个问题:首先0表示不唯一更麻烦的是算术运算变得更加复杂了加法为例:当把两个带符号整数相加的时候,如果两个数的符号相同,那么结果的符号不变,绝对值是这两个数的绝对值相加;如果两个数的符号相反,那么两个数的绝对值相减(大减小),并绝对值较大的加数的符号作为结果的符号。这样一次简单的加法操作涉及到了多次判断,比大小,甚至是减法。

出于上述考虑,计算机中采用补码来表示负整数。补码表示法不是很直观,但是简化了算术运算。以4二进制数举例:在补码中,第021含义和之前一样,分别表示124但是第31表示的不是8而是-8

24 4位二进制补码和十进制

二进制

十进制

二进制

十进制

二进制

十进制

二进制

十进制

0000

0

0100

4

1000

-8

1100

-4

0001

1

0101

5

1001

-7

1101

-3

0010

2

0110

6

1010

-6

1110

-2

0011

3

0111

7

1011

-5

1111

-1

以上表中的1010为例:第1位的1表示1231表示1-8所以合起来1010表示-8+2=-6可以试着选011171111-1加法,看看能否得到0110

现在大家已经知道了如何从一个二进制正整数算出对应的十进制正整数,那么,如果给定一个十进制正整数,怎样得到它对应的二进制数呢?通常采用的方法是除二取余法:把正整数不断除以2,将每次得到的余数连起来再逆序,就是对应的二进制表示。下面仍以十进制的13为例

  1. 13除以26余数是1
  2. 6除以23余数是0
  3. 3除以21余数是1
  4. 1除以20余数是1

所以13二进制表示就是1101

&多学一招:负整数的补码表示法

比求正整数二进制表示更加困难的是如何从负整数求出对应的二进制补码。在这里-3为例,求解对应的4二进制补码

  1. 绝对值的二进制表示:3二进制表示是0011
  2. 按位取反:0110得到1100
  3. 11100+1=1101这就是-3补码。

可以仿照上述过程求解负整数的补码。

M脚下留心:计算机究竟是存储十进制数,还是存储二进制数?

大家都会有这样的疑问:“讲了十进制数,又讲了二进制数,虽然知道计算机运算时用的是二进制数,但它存储的时候是存储十进制数呢,还是二进制数呢?”要解答这个问题,关键在于理解“数”和“数的表示”之间的区别。

对于一个数而言,在不同表示法下长的样子可能是不一样的。以上文讲的13为例,在十进制表示法下由两位数“1”和“3”组成,而在二进制表示法下由四位数“1”“1”“0”和“1”组成。尽管这两种表示法看上去不同,但是它们的值是相同的,仍然表示同一个数。

因此计算机存储的既不是十进制数,也不是二进制数——计算机存储的是数值本身;运算时是基于二进制的;至于显示到屏幕上时究竟是显示成十进制还是二进制,这就由大家写的程序决定了。

2.二进制的扩展:八进制与十六进制

十进制和二进制以外,还有两种在程序中经常使用的进制:八进制和十六进制。所谓八进制就是用07组成的数字,每一位的大小分别是1864512等等。比如八进制156换算成十进制就是164加上58加上6164+40+6=110

八进制和二进制之间有十分简单的转换关系:对于八进制数,只要将每一位都按照下表翻译成3二进制数,将它们连接在一起即可:

25 二进制数和八进制数

二进制

八进制

二进制

八进制

000

0

100

4

001

1

101

5

010

2

110

6

011

3

111

7

举例来说,八进制数156换算成二进制就应该是:001 101 110也就是1101110试着将它转换成十进制数2+4+8+32+64=110和八进制数一致。

反过来要将二进制数转换成八进制数也很简单:只要将二进制3一组,按照上表翻译成对应的八进制数码即可:比如下面这个16的二进制数:

1001 1100 1010 1011

首先按照3一组进行分组:

1 001 110 010 101 011

接着将它们转换成对应的八进制数字:

1 1 6 2 5 3

所以对应的八进制数就是116253。可以验证它们的十进制值都是40107

除了八进制,在程序中更加常用的是十六进制。十六进制用09AF分别表示01516值,每一位的大小分别是116162=256等等每一位表示的分别是160161162等等

26 二进制数和十六进制

二进制

十六进制

十进制

二进制

十六进制

十进制

0000

0

0

1000

8

8

0001

1

1

1001

9

9

0010

2

2

1010

A

10

0011

3

3

1011

B

11

0100

4

4

1100

C

12

0101

5

5

1101

D

13

0110

6

6

1110

E

14

0111

7

7

1111

F

15

二进制和十六进制的转换同二进制和八进制的转换非常类似:只要将二进制数4个一组按照上表进行转换即可。用十六进制替代二进制的一个好处就是书写非常方便。比如一个32位的int类型整数:

0111 1101 1010 1010 0011 0110 1100 1001

转换成对应的16进制整数就是:0x7DAA36C9。它的十进制大小是2108307145

&多学一招:利用windows计算器进行进制转换

Windows 自带的计算器可以方便地实现进制转换的功能,这里以Windows 7上的计算器为例进行简单介绍按住键盘上的windows(一般在空格键左侧,带有windows图标+R输入calc,可以快速启动计算器,如图2-2所示

图片1.png

启动计算器之后,在“查看”菜单下面找到程序员就可以进入专为程序员设计的计算器界面:

图片2.png

2-3中注意到左侧的进制选项,可以分别选择十六进制、八进制、十进制和二进制。选择十进制,并输入一个十进制整数,就可以在数字下方看到它的32bit表示,选择相应的进制就可以得到进制转换的结果。

&多学一招:如何将八进制和十六进制数转换成十进制?

上文介绍了如何在二进制和八进制、十六进制之间进行转换,那要如何将八进制和十六进制数转换为十进制呢?思路很简单:先将它们转换成二进制数,再根据前文中讲过的二进制到十进制的转换方法转换到十进制数即可。

例如对于十六进制数0xD53A,要转换成十进制数的话需要经过以下过程:

0xD53A = 1101 0101 0011 1010 (二进制) = 54586 (十进制)

八进制数04255,要转换成十进制数的话需要这样做:

04255 = 1000 1010 1101 = 2221

3有符号数和无符号数

有符号数就是用最高位表示符号(正或负),其余位表示数值大小比如:
     0011 表示 +3
      1011 表示 -3
    无符号数全部二进制均代表数值,没有符号位。即第一个"0""1"不表示正负。比如:
     0011 表示 3
      1011 表示 11
    C支持所有整型数据类型的有符号数和无符号数运算。尽管C标准并没有指定某种有符号数的表示,但是几乎所有的机器都使用二进制补码。通常,大多数数字默认都使有符号的,C也允许无符号数和有符号数之间的转换,当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么C会隐含地将有符号参数强制转换为无符号数,并假设这两个数都是非负的,来执行这个运算。

  • 基本数据类型

C语言为大家提供了不同种类的基本数据类型,其中包括整数类型、实型类型、字符类型等等,每一种数据类型用途都是不一样的,本节将针对C语言基本数据类型的相关知识进行详细地讲解。

2.1.1 整数类型

顾名思义,整数类型就是用来保存整数的数据类型。C语言中基本的整数类型为int一般长度为4字节32二进制数。

4int的基础上C语言提供short intlong intlong long int三种整型数据类型这三种类型名称中的int都可以省略不写,即略写为shortlonglong long。这几种类型都是用来保存整数的,除了名字不同之外,还有什么区别呢?顾名思义,“short”意味着短,“long”意味着长,用来存储数字的空间越大,能存储的最大数值就越大。因此一般而言,short型变量能存储的最大数值要比int型变量能存储的最大数值小,而long long型变量能存储的最大数值是最大的。

C语言标准中没有明确规定以上四种整型数据类型的长度,因此它们的长度由编译器自行决定。大家可以利用C语言提供的sizeof运算符查看自己计算机上这四种整型数据的长度。sizeof运算符接受数据类型的名称,返回以字节为单位的数据类型的长度。

Visual Studio中新建一个工程复制以下代码:

例程 21 查看数据类型的长度

/* DataTypeLength Project */
#include <stdio.h>
int main()
{
printf("%d\n", sizeof(int));
return 0;
}

程序的运行结果如图2-5所示:

图片3.png

25 int长度

编译运行该工程后,输出的结果是电脑上int型变量的长度。可以将sizeof后面的int换成shortlonglong long分别试验,输出的结果就是对应数据类型的长度

尽管C语言标准中没有明确规定整型数据的长度,但是有如下要求:首先short型变量的长度必须不短于2字节,long型变量的长度不短于4字节其次是几种数据类型的相对长度

sizeof(long long)  sizeof(long)  sizeof(int)  sizeof(short)

64位计算机系统利用VS编译上述例程得到以上各种数据类型的长度如下表所示,供大家参考,如表2-7所示

27整型数据的长度一些参考值

数据类型

字节

位数比特数)

short int

2

16

int

4

32

long int

4

32

long long int

8

64

现在大家清楚了,C语言能够理解如下整数类型:shortintlonglong long。对于short,计算机会准备2字节来保存对于int,计算机会准备4字节,对于long long,计算机会准备8字节。接下来第二个问题一定是:4字节的int究竟能表示多大的整数?

搞清楚这个问题,从十进制开始分析。如果有人问:3的十进制数可以表示的最大整数多少?”大家一定会很轻松地回答999因为对十进制太熟悉了

遗憾的是,计算机使用的是二进制。下面换一个问法324字节二进制数可以表示的最大整数是多少?”直觉上,答案应该是下面这个数:

1111 1111 1111 1111 1111 1111 1111 1111

一共32,每一位都取成最大1应该就是32二进制表示的最大整数了吧因为是二进制,每一位只能取0或者1所以就取成1吧!

这个数大概是十进制的40亿。然而40亿并不是int能够表示的最大整数——int的最高位被用来区别要表示的数是正数还是负数了,所以实际上int能够表示的最大整数是:

0111 1111 1111 1111 1111 1111 1111 1111

这个数大概是20亿int能够表示的最小负数大概也是20亿,所以现在应该有这样的概念:对于正负20亿以内的数,用int来表示足够了(这句话的意思是说:4字节就已经够表示数了,没有必要用8字节的long long,多了也浪费是不是?)。下面的表格列出了这几种数据类型能够表示的范围,供需要的时候对照:

28 各种整型表示范围

数据类型

字节

表示范围

short

2

-3276832767 (-215 ~ 215-1)

int

4

-21474836482147483647 (-231 ~ 231-1)

long long

8

-92233720368547758089223372036854775807(-263 ~ 263-1)

 

大家可能已经注意到了int的最高位被用来表示正负数了,如果能够保证不使用负数的话(比如说,定义一个整数来表示人的体重,体重总不可能取到负数的),那最高位不就没什么用了吗?对于这种情况,C中提供了4种非负类型的整数,称为无符号整型:

29 各种无符号整型长度和表示范围

数据类型

字节

表示范围

unsigned short int

2

065535 (0 ~ 216-1)

unsigned int

4

04294967295 (0 ~ 232-1)

unsigned long int

4

04294967295 (0 ~ 232-1)

unsigned long long int

8

018446744073709551615 (0 ~ 264-1)

无符号整型看上去很简单,只是在之前的数据类型前面加了一个unsigned,而且不改变字节数。不过,无符号整型只能表示非负整数了,但是非负整数的范围比原来的有符号整型要大,这是非常合理的:因为之前用来表示符号的最高位被用来存储数字。

在本小节结束对整型的讨论之前,还有最后一个疑问:

“如果定义的数超出了范围会怎么样?”

如果对计算机说:

“定义一个short类型的整数,这个整数的值是70000。”

计算机查看了一下上面的几张表,short类型,要准备2个格子……70000……等一下70000?好像超出范围了……”这个时候计算机会崩溃吗?它不会那么脆弱的,只不过它会对输入的70000做一些偷工减料的处理,从而把它塞到2个字节当中(当然,处理之后的这个数肯定不会是70000了)。具体的做法将在后文中讲解。

图片4.png

26 short型变量中存入70000

小结一下:计算机用2/4/8字节来表示一个有符号或者无符号的整数,不同的整数类型有不同的范围,大家可以根据想要表示的数选择合适的整数类型如果表示的值溢出了,计算机会自己偷偷做一些改动。

&多学一招:int类型的表示范围

int一共包含32,其中最高位可以区别正负,0表示正数1表示负数,所以int型变量能够表示的最大正整数应该是:

0111 1111 1111 1111 1111 1111 1111 1111

这个数换算成十进制就是230+229+228+……+20=231-1=2147483647这就是int能够表示的最大整数。

2.1.1 实型类型

实型数据是由整数部分和小数部分组成。除了正确地表示整数之外,还需要利用二进制正确地表小数。小数的表示复杂一些,但是思路和整数是一样的:C语言提供了两种类型,分别是浮点型float双精度浮点型doublefloat4字节,double8字节来表示小数,它们也都有各自能表示的范围。

210 浮点数的表示范围

数据类型

字节

表示范围绝对值

float

4

3.4e-383.4e38

double

8

1.7e-3081.7e308

当然这里有个问题需要注意首先,和整数不一样,小数可能无限多个,但是由于只有有限个字节,所以有些小数是注定无法准确表示的。比如

 

这是一个无限循环小数程序是没办法用32或者64位空间来精确表示的——因为它有无限多位此外,有些看上去很简单的十进制小数也没有办法用floatdouble来表示,比如0.6这是由于十进制二进制之间的转换方式导致的,所以很遗憾,在表示浮点数的时候,最好不要假设浮点数能够被计算机精确地表示

Visual Studio中新建一个C工程,如例程2-2所示

例程 22 浮点数0.6

/* float Project */
#include <stdio.h>
int main()
{
float f = 0.6f;
printf("%.10f\n", f);
return 0;
}

程序的运行结果如图2-7所示:

图片6.png

例程2-2中程序做了如下几件事情:

定义了一个float类型的数,值为0.6(后面f只是为了表示这个数是float类型,不影响0.6);

屏幕上输出这个数小数点后10的值。

运行程序,会发现得到的值是0.6000000238。计算机对于精确表示0.6无能为力,所以它只能尽量做到最好,用一个能表示的最接近的值来代替0.6

除了无法精确表示小数浮点数表示的数是有限的。对于float1038次方是能表示的极限,这意味着比这个数量级更小的正数,如1e-100float无法表示的(不过0可以精确表示的)也是由于浮点数的表示方式导致的。

小结C语言中用4字节的float或者8个字节double来表示一个浮点数但是不一定能精确地表示想要的小数。此外,不要float或者double去表示特别特别大,或特别特别的数。当然大部分情况下,floatdouble提供的范围已经够用了。另外floatdouble也可以表示整数,毕竟,整数也是一种“小数只不过小数部分0。遗憾的是,对于一部分特别大的整数,即使这个整数浮点数能够表示的范围之内,floatdouble也不能保证精确地表示。总之使用浮点数时,在任何情况下都不要假设提供的数被精确表示了——除非完全明白浮点数的表示方法。

2.1.1 字符类型

除了表示整数和浮点数,C语言中还需要表示的一类数据就是字符。最常见的字符就是英文字母了,假设现在本章开头那台高度智能的计算机说:

“定义一个英文A

对于计算机来说,它只懂二进制,它只知道为大家想要的分配几个小格子(字节然后每个格子里面填上80或者1它是无法直接理解英文字母的。这时候就需要有人告诉计算机一个对应关系:

存一个英文字母A的时候,就分配一个小格子给,然后里面存一个十进制数65

大体上,计算机就是这样存储字符的。这种用来存储字符的数据类型叫做char,翻译成中文字符型,长度为一个字节。一张名为ASCII码表”的表格负责把常见的字符对应到一个字节长的整数A对应到65B对应到66C对应到67空格键对应到32a对应97,等等计算机每次碰到一个字符的时候,就去ASCII码表当中去查对应的整数,然后把这个整数存起来,于是字符就被保存下来需要使用这个字符的时候,计算机就把格子里的整数取出来,然后查ASCII码表,找到对应的字符。下表是常见字符到ASCII编码的对应。完整的ASCII码表请参照下一节中,如表2-11所示:

211 常见字符的ASCII

二进制表示

十进制

对应字符

0010 0000

32

空格

0011 0000

48

0

0011 0001

49

1

0011 1001

57

9

0100 0001

65

A

0100 0010

66

B

0101 1010

90

Z

0110 0001

97

a

0110 0010

98

b

0111 1010

122

z

大家肯定已经意识到了,既然只用一个格子来表示字符,而一个格子能表示的数肯定很少(其实只有256个),所的这张ASCII码表应该也很短,能表示的字符也很少。确实这样,而且比想象的还要短——基本ASCII码表里面只有128字符,但是对英文来说这已经够用了。

根据上面的描述,char类型本质上就是长度为一个字节的整数,所以它也可以intshort那样用来保存整数也可以在前面加上unsigned变成unsigned char来保存无符号整数。请看下面的例子:

Visual Studio中新建一个C工程,如例程2-3所示

例程 23 ASCII码字符

/* ASCII Project */
#include <stdio.h>
int main()
{
char ch = 'A';
printf("%c %d\n", ch, ch);
return 0;
}

程序的运行结果如图2-8所示:

图片7.png

例程2-3中首先定义了一个char类型的英文字母A然后两种形式输出:%cch看成是字符输出,%dch看成是整数输出。最后输出的结分别是A65

可以试着把第一句改成

char ch = 65;

重新编译并运行现结果是一样的这说明对于char来说,A65其实什么区别

&多学一招为什么计算机要采用二进制

大家可能会奇怪,十进制相比,二进制表示同样的数占用更多的位数,使用者来说更加不直观,为什么计算机还要选择采用二进制来存储和运算呢?这是因为计算机实际上是以电子元件的状态来保存数据的。具有两种稳定状态(如电压的高与低,二极管的通与断)电子元件是很容易制造的,因此使用二进制保存数据就顺理成章了。如果计算机要使用十进制,就意味着在不增加电子元件数量的前提下,每一个元件需要有十种稳定的状态来对应表示09这十种可能的值,这直接使用二进制要困难多了。

通过上一小节的学习,大家应该对C语言中的基本数据类型有了一些基本的认识。同时,也看到了很多的例子中都有这种语句:

float f = 0.6f;

char ch = 'A';

这些语句就在让计算机定义一个浮点数/字符。当然可以让计算机定义一个整型比如:

int a = 30;

这行语句就完成了之前的要求:定义一个整数30。为了完成工作,大家需要让计算机清楚这几件事情:

  1. 定义的是什么样的数据类型把它写在开头,比如:

int a = 30;

首先告诉计算机:定义一个int类型的整数。计算机其实是编译器)看到int之后,所有的信箱中找出4空闲的格子4字节),准备存放要定义的int

  1. 要给大家的信箱起一个名字

如果楼道口的信箱上没有各家的门牌号,那么所有的信箱看起来就完全一样了,邮递员就无法得知应该把信投到哪个信箱里。类似地,当在第一认领4格子之后,也要为这4格子起一个名字,不然计算机就不知道到哪一个格子当中去存放和寻找要保存的整数;在这里例程起了个很简单的名字a

  1. 把信件放进信箱(告诉计算机要定义的整数是多少)

在这里大家想要定义一个整数30,就把它写在等号的右边。在C语言中,=是一种赋值运算符,它表示把右边的数存到左边的格子里。在这个例子中,只要30这个值存到a所在的4个格子当中,整个工作就完成了!

图片8.png

本节将重点介绍第三中等号右边的0.6f”和“30”这样的量怎么写,这些量被称为常量——计算机按一定规则办事的,它不像人那么高端,能够理解各种自然语言。比如人与人之间可以说

定义一个整数105次方

理解起来毫无问题,但是如果和计算机说:

int a = 105次方;

那么很遗憾,计算机是不能理解等号右边这个值的。

即使换成英文也不行:

int a = 10 to 5;

必须写成:

int a = 100000;

2.1.1 ASCII码表

下表为ASCII码表的可打印字符部分(0 ~ 127),供大家查阅使用,如表2-12所示:

212 ASCII码表

代码

字符

代码

字符

代码

字符

代码

字符

0

 

32

[空格]

64

@

96

`

1

 

33

!

65

A

97

a

2

 

34

"

66

B

98

b

3

 

35

#

67

C

99

c

4

 

36

$

68

D

100

d

5

 

37

%

69

E

101

e

6

 

38

&

70

F

102

f

7

 

39

'

71

G

103

g

8

退格

40

(

72

H

104

h

9

Tab

41

)

73

I

105

i

10

换行

42

*

74

J

106

j

11

 

43

+

75

K

107

k

12

 

44

,

76

L

108

l

13

回车

45

-

77

M

109

m

14

 

46

.

78

N

110

n

15

 

47

/

79

O

111

o

16

 

48

0

80

P

112

p

17

 

49

1

81

Q

113

q

18

 

50

2

82

R

114

r

19

 

51

3

83

S

115

s

20

 

52

4

84

T

116

t

21

 

53

5

85

U

117

u

22

 

54

6

86

V

118

v

23

 

55

7

87

W

119

w

24

 

56

8

88

X

120

x

25

 

57

9

89

Y

121

y

26

 

58

:

90

Z

122

z

27

 

59

;

91

[

123

{

28

 

60

<

92

\

124

|

29

 

61

=

93

]

125

}

30

-

62

>

94

^

126

~

31

 

63

?

95

_

127

 

2.1.2 枚举

枚举用来一系列整型常量命名。的使用方法如下:

enum 枚举 {标识符1 = 整型常量1, 标识符2 = 整型常量2, ...}

和宏定义一样,枚举中的标识符习惯上也使用大写字母和下划线来命名。下面是用枚举定义十二个月份的例子:

enum month {JAN=1, FEB=2, MAR=3, APR=4, MAY=5, JUN=6,

JUL=7, AUG=8, SEP=9, OCT=10, NOV=11, DEC=12};

这样的定义比define要方便不少,但是依然有些冗。幸运是,在枚举中如果不指定标识符的具体值的话,默认标识符的值等于前一标识符的1。因此可以将上面的定义简化成:

enum month{JAN=1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC};

进一步地,如果不指定第一个标识符对应的常量,则它的默认值是0,如例程2-4所示:

例程 24 枚举的使用

/* Enum Project */
#include <stdio.h>
/* 定义一组常量 */
enum Constants {C1, C2, C3 = 4, C4, C5 = 3, C6, C7, C8 = '0', C9};
int main()
{
printf("C1=%d\n", C1);
return 0;
}

程序的运行结果如图2-10所示:

图片9.png

例程2-4中首先用枚举定义了一组常量C1C9,并Console窗口中输出常量C1数值。请大家在编译和运行该例程之前,先试着猜测C1C9九个常量的数值

  • 常量

2.1.1 整型常量

简单的整型常量是十进制整数,其声明规则为:

[正负][十进制整数][后缀]

如果声明的是一个十进制正整数[正负号]一项中的正号可写可不写[十进制整数],除非声明的常量0否则不要以0开头(即不要出现0123这样的数,而应直接写123[后缀]可以有以下几种取值(大小写随意):

213 整型常量的后缀

后缀

含义

L

明一个long类型的常量

LL

明一个long long类型的常量

U

明一个unsigned int类型的常量

UL

明一个unsigned long类型的常量

ULL

明一个unsigned long long类型的常量

如果没有后缀,常量的大小没有超出int的范围,则默认整型常量的类型是int

参考下面的几个例子理解整型常量:

214整型常量举例

常量

含义

123u

unsigned int类型的整型常量

123LL

long long int类型的整型常量

-124

int类型的负整型常量

十进制当中,150150015都是同一个值,但是如果写:

int a = 15;

int a = 015;

这两个值不一样。可以动手试试下面的程序:

Visual Studio中新建一个C工程,如例程2-5所示

例程 25 八进制整数

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

程序运行结果如图2-11所示:13

大家会发现最后输出了13。这是因为0打头的整数会被解释成八进制数,而不是十进制数。八进制的15刚好就是十进制的13

2.1.1 实型常量

实数常量也叫浮点数常量它表示的是一个浮点数的值。实数常量有两种表示方式:十进制小数表示和指数表示。用十进制小数表示时,实数常量的格式为:

[正负号][十进制整数部分][小数点][十进制小数部分][后缀]

其中小数点必须有正负号一项中如果正数的话正号可以不写,十进制整数部分或十进制小数部分0话可以省略不写,但如果同时为0话则至少要保留一个后缀部分只有两种取值(大小写不限F表示该常量的类型为floatL表示该常量的类型为long double如果没有后缀,那么默认常量的类型为double以下是一些正确的实型常量如表2-15所示:

  215 实数常量举例

常量

含义

.123l

long double类型的实型常量,它的值为0.123

.00

double类型的实型常量,它的值为0

15.f

float类型的实型常量,它的值为15.0

 

声明实数常量的另一种方法是指数表示法,其声明规则为:

[十进制实数][指数符号eE][十进制指数][后缀]

下面逐个解释各个部分的含义

  1. [十进制实数]可以是一个小数,或者整数
  2. [指数符号eE]e或者E表示10幂,大小写不限。
  3. [十进制指数]必须是整数
  4. [后缀]部分的规则和前述“实型常量的十进制小数表示后缀的规则一致。

以下是一些正确的声明方式:

216指数表示法常量举例

常量

含义

12.3E4f

float类型的常量,它的值123000

.3e4l

long double类型的常量,它的值为3000

1e-6

double类型的常量,它的值0.000001

 

简单地说:可以按照十进制小数的写法写,也可以用指数的形式去写指数形式中,指数必须是整数。

M脚下留心整型常量和实型常量后缀中的L

整型常量中,后缀L表示数据类型为long int而在实常量中,后缀L表示数据类型为long double实型常量0.123L为例在声明时,不要把小数点漏掉,否则常量变成0123L这是一个八进制long int类型的整型常量。

2.1.2 字符常量

下面特别容易和整型常量搞混的字符常量。字符常量是一对单引号将单个字符括起来的常量,它只有一种类型——char类型,占用81字节。以下是一些字符常量的例子

'A':字符常量A

'B':字符常量B

'0'字符常量0

' ' 字符空格

注意字符'0'整数0不一样的!如果去查ASCII表(下一节提供了ASCII码表供查阅),就可以看到字符'0'实际上整数48

在此前查阅ASCII码表的过程中应该留意到,ASCII码表中,除了可以直接从键盘上输入的字符(英文字母,标点符号,数字数学运算符以外,还有一些字符无法键盘直接输入的,例如表中值为13的回车——无法通过键盘直接输入来定义一个“回车字符常量因为当大家在VS中输入“回车”键盘上回车键)时候,实际效果就是在文本编辑器上光标跳到了下一行为了定义回车这个字符常量,需要采用一种新的定义方式:转义字符。反斜杠\开头,随后接特定的字符一些常见的转义字符定义如下:

216 部分常见转义字符

转义字符

对应字符

ASCII码表中的

'\t'

制表符Tab键)

9

'\n'

换行

10

'\r'

回车

13

'\"'

双引号

34

'\''

单引号

39

'\\'

反斜杠

92

Visual Studio中新建一个C工程,如例程2-5所示

例程 25 ASCII码字符

/* ASCII Project */
#include <stdio.h>
int main()
{
char ch = '\n';
printf("%c", ch);
return 0;
}

请大家试着运行这个程序,看看回车在屏幕上的显示效果。也可以试试上表中的转义字符

上面的“动手体验”中大家已经了解利用反斜杠定义字符常量的方法,值得一提的是反斜杠这个字符本身也必须转义的方式来定义否则反斜杠会将后面的单引号解释为转义字符而导致编译错误。

&多学一招:回车和换行的区别

大家应该在电影电视上见过打字机(不是打印机这种设备:打字员首先将纸张附着在打字机的滚筒上,当开始打字时,打字员敲击键盘输入一个字符,打字机的打字头在纸上打出相应的字符,随后滚筒带动纸向左移等待打字员的下一次输入。当打字员输入完一行即将开始输入新的一行时,打字员手动将滚筒向右推,使得打字头回到纸张上新一行的开头,接着滚筒会将纸张整体移动一行。前一个动作被称为回车(carriage return,后一个动作被称为换行(line feed。在现代计算机上,这两个字符被保留了下来,用作文本文件中的行分隔符然而,不同的操作系统对于究竟选择使用回车还是换行作为行分隔符有分歧的Windows操作系统中使用的是“回车+换行组合,Unix采用的是换行Mac OS采用的是回车。下面就以Windows下的文本文件为例向大家展示Windows的行分隔符

打开记事本,键入以内容,两行文本用正常的回车键分隔,如图2-13所示

图片10.png

213 Windows的一个文本文件

随后请大家将文本文件保存,注意保存时编码选择ANSI(它ASCII字符集基础上扩展出的字符编码,这是Windows记事本保存文本文件时的默认编码)。接下来,利用带有十六进制查看功能的文本编辑器Sublime Text打开选择利用16进制查看该文本文件,如图2-14所示

图片11.png

 214 16进制下查看文本文件

其中0x31是数字1ASCII码(十进制490x0d0x0a分别是回车和换行的ASCII码(十进制分别为13100x32数字2ASCII码。

2.1.1 宏定义

文中已经介绍了各种类型常量的定义方法。有些时候,希望能够给常量一个别名,从而更加直观地表示常量的具体含义。比如定义了一个实数常量3.1415926希望能够给这个常量起一个更加易懂的名字PI方便阅读和编写程序。C语言中提供了define指令来解决这一问题。define指令的基本格式为:

#define 别名 常量

其中“别名”习惯上采用大写英文字母和下划线命名,如JANUARYMAX_LINE_NUMBER”、“PI”等。下面是一个例子:

#define FEB 2

在这个例子中,程序常量2起了一个别名FEB,之后只要是出现FEB的地方都会被程序认为是2

define指令所做的事情非常非常简单:为指令中的“常量”一个叫做“别名”的名字。define指令之后的程序中,凡是出现了这个别名的地方,一般都直接替换为对应常量,如例程2-6所示:

例程 26 #define用法示例

/* Define Project */
#include <stdio.h>
#define PI 3.1415926f
int main()
{
           /* 定义圆的半径为1*/
          float radius = 1.0f;
         /* 计算圆的面积 */
          float area = PI * radius * radius;
         /* printf输出语句:圆的面积是*/
        printf("%f\n", area);
       return 0;
}

程序的运行结果如图2-15所示:3.141593

215define用法

例程2-6定义了一个半径为1的圆,计算圆的面积并输出。程序利用define指令为常量3.1415926f定义了一个标识符PI在编译程序之前,编译器会将程序define指令之后出现的PI直接替换成常量3.1415926f(记住define是预处理指令它和include一样会在编译整个程序之前被处理掉)

利用define定义符号常量有以下几点优势:

  1. 增强程序的可读性。和冷冰冰的数字相比,PI这样的标识符显然可以提供更多意义的信息。尤其是规模较大的程序中,程序员很难记住每一个常量的具体含义的,标识符的引入可以给编程中的程序员以有益的提示
  2. 便于对常量进行修改。假设在例程3-3想要提高程序的计算精度,因此采用一个更加精确的PI141592653589793238463引入了符号常量PI之后,只要#define指令中原有PI常量修改为新的值即可如果没有引入符号常量,而是在程序中每一处使用了PI值的地方都直接输入3.1415926(这种将常量直接编写在程序中的方式通常被称为硬编码就需要找到每一处3.1415926进行替换当一个常量程序中多次出现时,这样的修改是非常耗时的,而且还可能导致常量前后的不一致,从而引入潜在的错误。

 

  • 变量

2.1.1 变量的定义

在程序运行期间,随时可能产生一些临时数据,应用程序会将这些数据保存在一些内存单元中,每个内存单元都用一个标识符来标识。这些内存单元我们称之为变量,定义的标识符就是变量名,内存单元中存储的数据就是变量的值。

定义变量的语法如下:

变量类型 变量 = 变量初值;

例如定义一个int类型的变量sum,并且令它的初值为0。其示例代码如下

int sum = 0;

这条语句含义,定义一个名叫sum的变量,它的数据类型int因此,程序在内存找到4空闲的字节并且把整数0写到4字节中。

在定义变量时也可以不给出变量的初值,示例代码如下:

int sum;

此时告诉了计算机,定义一个int类型变量sum在内存中分配4字节的存储空间,但还没有为他赋值。此时变量sum对应4个字节在内存中的值是完全不确定的

在程序中,还可以一次定义多个变量,示例代码如下

int a, b = 30, c;

上述代码中定义了三个int类型的变量,并分起名为abc。不同的变量之间用逗号分隔,语句末尾用分号作为结束不同的变量可以进行赋值也可以不赋值。

2.1.2 C语言关键字

关键字是编程语言里事先规定好特殊含义的词,有些资料上称为“保留字”。C89标准规定了如下32个关键字。为避免冲突,在命名变量时不应该使用它们,如表2-17所示:

217 C89标准中的关键字

auto

double

int

struct

break

else

long

switch

case

enum

register

typedef

char

extern

return

union

const

float

short

unsigned

continue

for

signed

void

default

goto

sizeof

volatile

do

if

static

while

这些关键字无需记忆,只要了解即可。随着C语言学习的逐步深入,大家会慢慢知道每个关键字的独特作用的。

C99标准新增了五个关键字:_Bool_Imaginary_Complexrestrictinline。请同样在变量命名时避免使用这五个关键字。

2.1.3 变量的使用

定义好变量之后,就可以对变量进行操作了,例如,读取变量的值。在前面的讲解中可以看到很多printf读取并输出变量的例子,接下来通过具体的案例来演示如何输出变量的值,如例程2-7所示:

例程 27输出变量的值

  • #include <stdio.h>
  • int main()
  • {
  • int a = 30, b = 25;
  • a = b + 3;
  • printf("%d\n", a);
  • return 0;
  • }

程序的运行结果如图2-16所示:28

例程2-7中,首先定义了两个变量ab然后将b+3赋值给a。在计算b+3,首先去读取b当前的值25然后加上3得到2828写给a,最后通过printf语句输出a当前的值。

1、变量的命名规范

上文提到使用一个变量之前需要先给变量起好名字名字当然不是随便起的,C语言对变量名如下几条限制:

  1. 变量只能由字母,数字下划线组成第一个字符不能是数字。合法的变量名包括:a1m_bufferpName等。
  2. 不能使用C语言的保留字:保留字就是C语言自己预先定义好的一些名字,比如之前看到returndoubleintcharinclude等等。这些名字C语言自己要使用,如果大家在程序中定义了一个叫做returnint变量,如下所示:

int return;

return;

C语言分不清楚大家究竟是想返回还是想定义变量的

尽管有上述两条限制,程序员的变量名还是非常多的,如何为自己的变量起一个好名字也是一门学问。假设想要在程序中自然数1加到100和,这个和存在一个int类型的变量里,可以叫它aaa,也可以叫它sum它们都是合法的变量名,但是显然后者比前者要得多因为sum一词指出了这个变量的含义和作用

2、常见变量命名

给变量起一个漂亮又有意义的名字是一门学问。此向大家介绍两种常用的命名规则

匈牙利命名法:匈牙利命名法建议使用一个简短的小写字母前缀来标注变量的类型,随后接若干首字母大写的单词来表明变量的作用,比如刚刚sum,在匈牙利命名法中会被建议命名为iSum:首字母i表示这是一个int类型的变量,Sum指示了这个变量的含义是某些数的和。其他一些常用的前缀包括:

fpSpeedfpfloat point浮点数缩写,这是一个用来记录速度的浮点数变量

strStudentNamestrstring字符串缩写,这是一个用来记录学生姓名的字符串。

u8Coloruunsigned(无符号整形)缩写,这是一个用来表示颜色的无符号整数。

匈牙利命名法的前缀并没有严格的规定,程序员也可以发明自己觉得合适的前缀

驼峰命名法:变量名由一系列首字母大写的单词组成,比如AccountNumberFirstNameStudentID等等。也有人将变量的第一个单词的首字母小写:accountNumberfirstNamestudentID。前者被称为大驼峰式命名法Pascal命名法,后者被称为小驼峰式命名法。

变量的命名法更多地是出于方便程序员阅读和编程的考虑,编译器只认变量名是否合法,而不会管变量名是否“有意义”。对于开发者来说,变量的命名法也没有高下之分,只要在自己的程序中做到变量命名法则前后一致可以了。

  • 类型转换

C程序中,有时候会遇到在两种不同类型的数据之间进行转换的情况比如利用一个float类型的浮点数给double类型的浮点数赋值,或者利用两个int整数相加的结果保存在long 类型变量当中。由于不同的数据类型约定了不同表示数据的方式,因此就需要有一些特定的规则来规定如何在不同的数据类型之间进行转换

2.1.1 类型提升

在表达式中,charshort 类型的值,无论有符号还是无符号,都会自动转换成int unsigned int 类型。举例说明:

char c1=12,c2=108;

c1+c2;

c1c2都是char型,计算表达式c1+c2会先将c1c2转换成int 类型,然后再相加,由于c1c2是被准换成表示范围更大的类型,故而把这种转换称为“提升”。

不光在表达式中,在函数中也会出现“提升”当作为参数传递给函数时,charshort类型会提升成int类型,而float类型则会提升成double类型,如:

char ch= a;

printf(ch保存的ASCII码值:%d\n,ch);

chchar型,作为参数传递给printf函数时,自动提升为int类型,因此不用再将ch强制转换为int 类型。

2.1.2 类型下降

在赋值语句中,赋值运算符左侧的变量是什么类型,右侧的值就要转换成什么类型。这个过程可能导致右侧值的类型提升,也可能导致其类型下降。所谓“下降”是指等级较高的类型被转换成等级较低的类型,也叫强制类型转换如下:

int d;

double x=3.1415926;

d=(int)x;//强制转换

x的级别比d高,因此将x的值赋给d会先将x的值转换为与d同样的类型,这将导致x的值的类型下降(针对x的副本而言),下降可能会丢失数据,因此大部分编译器会发出警告,为避免发出警告,需要进行强制转换。另外需要注意的是,假如右值过大,超出了左值的取值范围,那么强行赋给左值,会导致左值溢出。

2.1.3 常用算术转换

C语言算术表达式的计算,在计算过程中,每一步计算所得结果的数据类型由参与运算的运算对象决定,相同数据类型的两个对象运算,结果数据类型不变,不同数据类型的运算对象进行运算,结果的数据类型由高精度的运算对象决定。精度的高低:double>float>int

需要注意的是,数据类型的转换是在计算过程中逐步进行的,整个表达式结果的数据类型一定与表达式中出现的精度最高的数据相同,但是具体得到数据值是逐步得到的,例如:

int x=1,y=3; double k=1573.267;

x/y*k

这个表达式计算结果的数据类型是double, 计算结果的答案是 0.0。因为在第一步 x/y 的计算中 结果是一个整型数据 0。第二步计算 0 * 1573.267 结果是一个double类型的数据,但数值是0.0。也就是说,算术表达式计算结果的数据类型与运算的优先级没有关系,一定具有表达式中精度最高的数据类型,但是具体得到数据结果数值,与优先级可就有关系。

2.1.4 赋值中的类型转换

当赋值运算符两边的运算对象类型不同时,将要发生类型转换,转换的规则是:把赋值运算符右侧表达式的类型转换为左侧变量的类型。具体的转换如下:

1 浮点型与整型

浮点数(单双精度)转换为整数时,将舍弃浮点数的小数部分,只保留整数部分。将整型值赋给浮点型变量,数值不变,只将形式改为浮点形式,即小数点后带若干个0。注意:赋值时的类型转换实际上是强制的。

2 单、双精度浮点型

由于C语言中的浮点值总是用双精度表示的,所以float 型数据只是在尾部加0延长为double型数据参加运算,然后直接赋值。double型数据转换为float型时,通过截尾数来实现,截断前要进行四舍五入操作。

3 char型与int

int型数值赋给char型变量时,只保留其最低8位,高位部分舍弃。

char型数值赋给int型变量时, 一些编译程序不管其值大小都作正数处理,而另一些编译程序在转换时,若char型数据值大于127,就作为负数处理。对于使用者来讲,如果原来char型数据取正值,转换后仍为正值;如果原来char型值可正可负,则转换后也仍然保持原值, 只是数据的内部表示形式有所不同。

4 int型与long

long型数据赋给int型变量时,将低16位值送给int型变量,而将高16 位截断舍弃。(这里假定int型占两个字节)。 将int型数据送给long型变量时,其外部值保持不变,而内部形式有所改变。

5 无符号整数

    将一个unsigned型数据赋给一个占据同样长度存储单元的整型变量时(如:unsigned→intunsigned long→longunsigned short→short) ,原值照赋,内部的存储方式不变,但外部值却可能改变。

将一个非unsigned整型数据赋给长度相同的unsigned型变量时, 内部存储形式不变,但外部表示时总是无符号的。

  • 技术总结

本章主要介绍了学习C语言所需的基础知识。其中包括C语言中的二进制、基本数据类型、变量和常量的定义,类型转换等,通过本章的学习初学者能够掌握C语言程序中如何进行进制转换,以及正确的定义变量和常量,并根据变量的数据类型不同进行类型转换等。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。