荡胸生层云:C语言开发修行实录
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第2章 看人生算法

算法是一个程序的灵魂,它决定了程序该如何运行。任何语言程序都离不开数据类型,每个数据都有其对应的数据类型,例如有数字类型、整型、浮点型和时间类型等。C语言程序也有自己的数据类型,通过对数据的定义来实现某些特定的功能。在本章的内容中,将详细介绍C语言中算法和数据类型的基本知识,并通过具体实例的实现过程来讲解。

2.1 引出问题

9月9日,22:00,雷雨

躺在宿舍的床上,风声、雨声和读书声,仿佛还环绕在耳边。十年寒窗,只为此刻开始的大学生涯。都说大学是人生的象牙塔,只要进入了大学校园,以后的路将十分平坦。事实是这样吗?我见到的是新闻媒体上对大学生就业难的宣传,大学四年难道是在浪费青春吗?

9月10日,7:00,多云

我:“大学生现在就业压力很大,真怕毕业后找不到工作!”

KNOWALL:“现在考虑这个问题好像有一点早吧!其实人活着需要面对很多问题:大学生要面对就业压力,职员要面对升职压力,老百姓面对物价的压力……如图2-1所示。但是你怎样应对压力才是最关键的问题,每个人所走的路不同,走什么样的路是自己选择的,这就需要你好好规划你的人生算法!”

图2-1 人生的算法

我:“人生算法?”

2.2 何谓算法

9月10日,7:20,多云

我:“在数学中,算法是一种计算方式!”

KNOWALL:“对,先给你看下面的泡茶问题。”

“烧水泡茶”有5道工序:

1.烧开水2.洗茶壶3.洗茶杯4.拿茶叶5.泡茶。

烧开水、洗茶壶和茶杯、拿茶叶是泡茶的前提。各道工序用时表:烧开水15分钟,洗茶壶2分钟,洗茶杯1分钟,拿茶叶1分钟,泡茶1分钟。

方法1:

第一步:烧水;

第二步:水烧开后,洗刷茶具,拿茶叶;

第三步:沏茶。

方法2:

第一步:烧水;

第二步:烧水过程中,洗刷茶具,拿茶叶;

第三步:水烧开后沏茶。

KNOWALL:“无论是方法1还是方法2都是一种算法,算法就是解决问题的方法,计算机的算法就是解决计算机问题的方法!”

2.2.1 算法的概念

在现实工作中,做任何事情都有一定的步骤。为解决一个问题而采取的方法和步骤,就称为算法。而计算机领域中的算法被称为计算机算法,计算机算法可分为如下两类。

● 数值运算算法:用于求解数值。

● 非数值运算算法:用于事务管理领域。

看下面的运算:

1×2×3×4×5

为了计算上述运算,通常需要按照如下步骤来计算。

第1步:先求1×2,得到结果2;

第2步:将步骤1得到的结果2乘以3,得到结果6;

第3步:将6再乘以4,得到结果24;

第4步:将24再乘以5,得到结果120。

上述过程就是一个算法,虽然过程有一点复杂。而在计算机程序中,对上述算法进行了改进,使用如下算法。

第1步:使t=1;

第2步:使i=2;

第3步:使t×i,乘积仍然放在在变量t中,可表示为t×i→t;

第4步:使i的值+1,即i+1→i;

第5步:如果i≤5,返回重新执行步骤3以及其后的步骤4和步骤5;否则,算法结束;

上述算法方式就是数学中的“n!”公式。

再看下面的数学应用题:

有80个学生,要求将他们之中成绩在70分以上者的信息打印出来。

在此设n表示学生学号,ni表示第i个学生学号;fen表示学生成绩,feni表示第i个学生成绩。则对应算法表示如下。

第1步:1→i;

第2步:如果feni≥70,则打印ni和feni,否则不打印;

第3步:i+1→i;

第4步:若i≤80,返回步骤2,否则,结束。

2.2.2 用流程图表示算法

9月10日,7:50,多云

我:“原来算法也需要表示方法!”

KNOWALL:“算法的表示方法即算法的描述和外在表现,在上节中的算法都是通过语言描述来体现的。除了语言描述外,还可以通过流程图来描述。在现实应用中,流程图的描述格式如图2-2所示。”

图2-2 流程图描述说明

我:“流程图我也画过,流程图可以更加形象地表示运作流程。”

KNOWALL:“让我来考考你:有80个学生,要求将他们之中成绩在60分以上者的成绩打印输出。”

10分钟后,我画的流程图如图2-3所示。

图2-3 算法流程图

KNOWALL:“不错,孺子可教!”

流程图通常包含如下三种结构。

● 顺序结构:顺序结构如图2-4所示,其中A和B两个框是顺序执行的。即在执行完A以后再执行B的操作。

图2-4 顺序结构

● 选择结构:选择结构也称为分支结构,如图2-5所示。此结构中必须包含一个判断框,根据给定的条件是否成立而选择是执行A框还是B框。无论条件是否成立,只能执行A框或B框之一,也就是说A、B两框只能有一个,也必须有一个被执行。若两框中有一框为空,程序仍然按两个分支的方向运行。

图2-5 选择结构

● 循环结构:循环结构分为两种,一种是当型循环,一种是直到型循环。直到型循环是先判断条件P是否成立,成立才执行A操作,而直到型循环是先执行A操作再判断条件P是否成,如果成立又执行A操作,如图2-6所示。

图2-6 循环结构

2.2.3 用C语言表示算法

9月10日,8:20,多云

我:“讲了半天现在才进入正题!用C语言表示算法需要注意什么?”

KNOWALL:“通过前面的知识你已经理解算法和流程图了!如果用C语言表示算法,必须严格遵循其语法规则。例如求“1×2×3×4×5”的乘积,可以通过如下代码实现。”

    main(){
      int i,t;
      t=1;
      i=2;
      while(i<=5){
        t=t*i;
        i=i+1;
      }
      printf("%d",t);
    }

2.3 不同的数据类型,品百态人生

9月10日,8:50,晴

我:“C语言中的数据有不同的类型吗?”

KNOWALL:“当然,不同的数据类型能实现不同的功能。例如在手机的输入法中,数字能实现电话拨号,字符能实现英文输入!”

我:“为什么用不同的数据类型来表示?”

KNOWALL:“数据不同,就可以处理不同类型的问题,这样便于事物处理和维护!其实何止是C中的数据,现实中的人也有不同的类型。例如可以将身边的同学分为:善良淳朴、口蜜腹剑、吹牛拍马、尖酸刻薄、挑拨离间等类型!”

在C语言中的数据类型可分为如下4大类。

(1)基本数据类型:基本数据类型最主要的特点是,其值不可以再分解为其他类型。也就是说,基本数据类型是自我说明的。

(2)构造数据类型:构造数据类型是在基本类型基础上产生的复合数据类型。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或是一个构造类型。

(3)指针类型:指针是一种特殊的,同时又是具有重要作用的数据类型。其值用来表示某个变量在内存储器中的地址。虽然指针变量的取值类似于整型量,但这是两个类型是完全不同的量,因此不能混为一谈。

(4)空类型:空类型是一种特殊的数据类型,它是所有基本类型的基础,C语言中的空类型用void关键字标示。在调用函数值时,通常应向调用者返回一个函数值。这个返回的函数值是具有一定的数据类型的,应在函数定义及函数说明中给以说明。但是,也有一类函数,调用后并不需要向调用者返回函数值,这种函数可以定义为“空类型”。其类型说明符为void。在后面函数中还要详细介绍。

2.4 变量和常量,体会变和不变

9月10日,10:50,晴

我:“变量和常量很容易理解,变量就是可以变化的量,常量就是值不变的量!”

KNOWALL:“对,在程序执行过程中,其值不发生改变的量称为常量,其值可变的量称为变量。两者可和数据类型结合起来进行分类,例如可分为整型常量、整型变量、浮点常量、浮点变量、字符常量、字符变量、枚举常量、枚举变量。在程序中,常量是可以不经说明而直接引用的,而变量则必须先定义后使用。”

2.4.1 不变的常量

在程序执行过程中,常量的值不发生变化。C语言的常量分为如下两种。

1.直接常量

直接常量是直接以字面形式即可判别的常量,也称之为字面常量。直接常量可以在代码中直接输入数值,例如下面的代码:

    int mm=10;
    float nn=1.01

2.字符常量

符号常量用一个标识符来表示,定义C符号常量的方法有两种。

(1)定义编译指令#define

编译指令#define定义常量的格式如下:

    #define常量名 常量值

其中“常量名”遵循的规则和变量相同,习惯上用大写字母表示符号常量名,小写字母表示变量名。看下面的代码:

    #define zz 111111
    float nn=12.0001

在上述代码中,将直接常量值“111111”用字符常量“zz”来代替。

(2)定义关键字const

const是一个修饰符,在定义一个常量时需要在子常量前加上此修饰符。在现实开发中,经常需要在整个程序中的许多地方都要用到一个常数,可以给这个常数取一个名字,每处常数都以该名字代替。

例如,可以用pi来表示π值:

    const float pi=3.14159000;

由于有效位的限制,在下面常量定义中,小数点后最后3位不起作用:

    const float pi=3.14159000;

虽然等号后面的常数是double型的,但是float常量只能存储7位有效位精度的实数,所以pi的实际值为3.14159000。如果将常量pi的类型改为double型,则能全部接受上述10位数字。

定义成const后的常量,程序中对其只能读不能修改,从而可以防止该值被无意修改。由于不可修改,所以,常量定义时必须初始化。例如下面的代码:

    const float pi;
    pi=3.141;

常量名不能放在赋值语句的左边。

常量定义中初始化的值可以是一个常量表达式。常量在程序运行之前就已经知道了其值,所以,编译时就能求值。但表达式中不能含有某个函数。例如下面的代码:

    const int size=100 * sizeof(int);
    const int number=max(15,22);

因为sizeof不是函数,而是C++的基本操作符,该表达式的值在编译之前能确定,所以上述代码中第一个常量定义语句合法。第二个语句要求函数值,函数一般都要在程序开始运行时才能求值,该表达式不能在编译之前确定其值,所以是错误的。

一般来说,相同类型的变量和常量在内存中占有相同大小的空间。只不过常量不能通过常量名去修改其所处的内存空间,而变量却可以。

2.4.2 可变的变量

9月10日,11:30,晴

我:“变量有什么特点?”

KNOWALL:“变量的值可以改变,一个变量对应一个名字,并在内存中占据一定的存储单元。变量定义必须放在变量使用之前,一般放在函数体的开头部分。”

我:“变量很重要吗?”

KNOWALL:“任何一种编程语言都离不开变量,特别是数据处理型程序,变量的使用非常频繁,没有变量参与,程序甚至无法编制,即使编制运行后的意义也不大。变量之所以重要,是因为变量是编程语言中数据的符号标示和载体。”

1.变量的类型

C语言中的变量类型有如下几种。

● 数据类型的变量:如char cHar,int iTimes,flaot faverage。

● 全局变量或者叫全程变量

● 局部变量

● 静态变量:有静态全局变量和静态局部变量,修饰字符是static。

● 寄存器变量:修饰字符是register。

● 外部变量:修饰字符是extern。

在C语言中,变量在内存中占用的大小由数据类型决定,不同的数据类型的变量,为其分配的地址单元数是不一样的。C语言中常用的数据类型有bool型、char型、short型、int型、long型、float型、double型。除上述几种基本的数据类型外,用户还可以自己定义所需要的数据类型。

2.变量作用域

我:“变量也有作用域?”

KNOWALL:“变量的作用域就是变量的可使用范围!

在函数内部说明的变量为局部变量,只有在函数执行时,局部变量才存在。当函数执行完退出后,局部变量也随之消失。即当函数执行完退出后,原先在函数内定义的变量现在不能用了,这通常由编译器保证,它会阻止编译通过。也就是说,原来为局部变量分配的内存,现在已经不属于它,它无权访问了。如要再使用这些内存单元就必须从新定义变量来申请,只有分配给的变量才可访问它。否则就会出错,如数组越界访问。

与局部变量不同,全局变量在整个程序都是可见的,可在整个程序运行过程中,对于任何一个程序都是可用的。全局变量的说明的位置在所有函数之外,但可被任何一个函数使用,读取或者写入。如下面的代码所示:

    int zz;                                        //定义全局变量
    int min(int x,int y);
    void main(){
    int a,b;                                       //定义变量
    printf("\nEnter two Number:");                 //调用库函数,输出函数
    scanf("%d,%d",&a,&b);                          //调用库函数,输入函数
    m=min(a,b);                                    //调用用户定义的函数
    printf("Minimum:%d\n",zz);
    }
    int min(int x,int y)                           //定义函数
    {
    int t=0;                                       //声明变量
    //函数体
    if(x<y) t=x;
    else t=y;
    return(t);
    }

在上述代码中,变量“zz”是全局变量,所以它可以在函数main内部使用,也可以在函数min内部使用;而变量“a”和“b”是在函数main内部定义的,所以只能在函数main的内部使用。

3.变量标识符

在C语言中,把用来标识变量名、符号常量名、函数名、数组名、类型名、文件名的有效字符序列称为标识符。标识符是一个名字,C语言中的标识符必须尊需如下4个原则。

● 第一个字符必须是字母(不分大小写)或下画线(_)。

● 后跟字母(不分大小写)、下画线(_)或数字;

● 标识符中的大小写字母有区别。例如变量sum,sum,sum代表三个不同的变量;

● 不能与C编译系统已经预定义的、具有特殊用途的保留标识符(即关键字)同名。

例如,不能将标识符命名为float、auto、break、case、this、try、for和unsigned等。

4.声明变量

C变量的声明格式如下:

    变量类型 变量命;

例如,下面的代码分别声明了int类型的变量m和float类型的变量n。

    int m;
    float n;

在同行中可以同时声明多个变量,每个变量名称之间用逗号(,)隔开。如下面的代码所示:

    int m,n;
    float a,b;

在下面的内容中,将通过一个具体的实例来说明C语言变量的使用方法。

测试1:根据用户输入的数据计算出圆的周长和面积

解决思路:我的设计思路如下所示。

(1)定义一个符号常量;

(2)定义三个变量,分别表示半径、面积和周长;

(3)通过PI*(2*r)计算周长,通过PI*(r)*(r)计算面积;

(4)通过printf输出计算结果。

编写程序文件为“matharea.c”,具体代码如下:

    #include <stdio.h>
    #define PI 3.14                         //定义一个符号常量
    int main()
    {
      float r,area,circ;                    //定义三个变量
      printf("\n输入半径:");                //提示用户输入圆半径的值
      scanf("%f",&r);
      circ=PI*(2*r);                       //计算圆周长
      area=PI*(r)*(r);                     //计算圆面积
      printf("\n周长是:%f",circ);
      getchar();                           //显示结果
      printf("\n面积是:%f",area);
      getchar();
    }

编写代码完毕后,按【F2】键将上述文件保存。然后按【F9】键编译并连接上述代码。按【Ctrl+F9】组合键运行上述代码,将首先输出“输入半径”字符,如图2-7所示。在屏幕中输入一个数字并按【Enter】组合键后返回Turbo C/C++ 3.0界面,然后按【Alt+F5】组合键后将分别输出此数据半径对应的周长和面积,如图2-8所示。

图2-7 运行界面

图2-8 计算结果界面

2.5 整型数据

9月10日,12:00,多云

我:“整型数据就是整数吧!”

KNOWALL:“整型数据的范围很广,分为整型常量和整型变量两种。整型常量的值在程序中是不变的!”

2.5.1 整型常量

1.整型常量的表示

C语言中的整型常量可以使用如下3种形式来表示。

● 八进制整常数

八进制整常数必须以0开头,即以0作为八进制数的前缀。数码取值为0~7。八进制数通常是无符号数。以下各数是合法的八进制数:

015(十进制数为13);0101(十进制数为65);0177777(十进制数为65535)

而以下各数不是合法的八进制数:

256(无前缀0);03A2(包含了非八进制数码);0127(出现了负号)

● 十六进制整常数

十六进制整常数的前缀为0X或0x。其数码取值为0~9,A~F或a~f。

以下各数是合法的十六进制整常数:

0X2A(十进制数为42);0XA0 (十进制数为160);0XFFFF (十进制数为65535)

而以下各数不是合法的十六进制整常数:

5A (无前缀0X);0X3H (含有非十六进制数码)

● 十进制整常数

十进制整常数没有前缀,其数码取值为0~9。

以下各数是合法的十进制整常数:

237;-568;65535;1627

而以下各数不是合法的十进制整常数:

023 (不能有前导0);23D (含有非十进制数码)

2.整型常量的类型

整型常量类型的具体说明如下。

(1)一个整数,如果其值在-32768~+32767范围内,认为它是int型,它可以赋值给int型和long int型变量。

(2)一个整数,如果其值超过了上述范围,而在-2147483648~+2147483647范围内,则认为它是长整型,可以将它赋值给一个lonl int型变量。

(3)如果某一计算机系统的C版本(例如Turbo C),确定short int型与int型数据在内存中占据的长度相同,则它的表数范围与int型相同。因此,一个int型的常量也同时是一个short int型常量,可以赋给int型或short int型变量。

(4)一个整常量后面加一个字母u或U,认为是unsigned int型,如12345u,在内存中按unsigned int规定的方式存放(存储单元中最高位不作为符号位,而用来存储数据)。如果写成-12345u,则先将-12345转换成其补码53191,然后按无符号数存储。

(5)在一个整常量后面加一个字母l或L,则认为是long int型常量。例如123l、432L、0L等,这往往用于函数调用中。如果函数的形参为long int型,则要求实参也为long int型。

2.5.2 整型变量

9月10日,12:30,多云

我:“整型变量的值在程序中是可变的!”

KNOWALL:“对,因为计算机只能识别二进制的数据,所以无论多么复杂的数据,最终都会被以二进制的形式存储在内存中。不同的数据类型在内存中的占用空间是不同的,对它们执行的数学运算符也不相同。例如,存储小型整数时需要的内存会较小,计算机对其执行的速度就非常快;而大型整数所占用的内存会比较大,计算机对其执行的速度就会更慢。所以编程领域中是否合理使用数据类型,对整个计算机效率有很大的影响。具体过程如图2-13所示。”

图2-13 数据在计算机内的存储

图2-13 人生的初始化

1.整型变量的分类

整型变量可以分为如下4类。

(1)基本型:类型说明符为int,在内存中占两个字节;

(2)短整量:类型说明符为short int或short。所占字节和取值范围均与基本型相同;

(3)长整型:类型说明符为long int或long,在内存中占4个字节;

(4)无符号型:类型说明符为unsigned。根据上述三种类型,无符号型又可以分为如下3种类型。

● 无符号基本型:类型说明符为unsigned int或unsigned。

● 无符号短整型:类型说明符为unsigned short。

● 无符号长整型:类型说明符为unsigned long。

各种无符号类型量所占的内存空间字节数与相应的有符号类型量相同。但由于省去了符号位,所以不能表示负数。通过使用signed,可以指定整型变量的符号是多余的,因为除非用unsigned指定为无符号型,否则整型都是有符号的。例如下面的变量是无符号型的:

    unsigned int                               //无符号基本整型
    unsigned long[int]                         //无符号长整型

C语言中默认格式是有符号数的,如果加上修饰符“signed”也是标识有符号数的。有符号整型变量的最大值为32767,无符号整型变量的最大值为65535。

Turbo C中各类整型量所分配的内存字节数及数的表示范围是不同的,具体如表2-1所示。

表2-1 整数类型

2.声明整型变量

声明整型变量的格式如下:

    类型 变量名;

其中“类型”可以是表2-1中所示的各种类型,例如下面的整型变量:

    int a,b,c;                             //a,b,c为整型变量
    long x,y;                              //x,y为长整型变量

测试2:使用整型变量计算两个整型变量的和。

解决思路:我的设计思路如下所示。

(1)声明变量a和b,并分别赋值;

(2)计算a和b的和并输出结果。

编写文件Addition.c,具体实现代码如下所示:

    #include <stdio.h>
    void main(){
      in   t a,b;                          //声明两个整型变量
      a=10;                                //赋值
      b=10000;                             //赋值
      printf("和是:%u\n",a+b);            //显示结果
      getchar();
    }

编译运行后将输出变量a和b的和,如图2-15所示。

图2-15 执行效果

2.6 实实在在的实型数据

9月10日,13:00,晴

闲暇无事,在日志中写下了下面一段话:

做人要实实在在,我会实实在在地生活,实实在在地工作,实实在在地享受……所有这些实实在在,需要有一个实实在在的人文环境,这是我们几代人梦寐以求的事情。要得到这些实实在在,需要我们每个人实实在在地做人和做事。

我:“整型数据和实型数据有区别吗?”

KNOWALL:“整型是整数,实型可以是小数!C语言中的实型数据分为实型常量和实型变量两种。实型常量的值在程序中是不变的,实型变量的值是可变的!”

2.6.1 不变的实型常量

实型也称为浮点型,实型常量也被称为实数或者浮点数。在C语言中的实数只采用十进制,它有如下两种形式。

(1)十进制数小数形式

十进制小数形式由数字0~9和小数点组成,并且必须得有小数点。例如下面都是合法的实数。

0.3、25.2、5.784、1.33、5.0、300.、-233.8230

(2)指数形式

指数形式由十进制数、加阶码标志“e”或“E”,以及阶码(指数)组成。但是在阶码标志“e”或“E”之前必须有数字,并且其后面的阶码必须为整数。其一般形式为:

    a E n(a为十进制数,n为十进制整数)

下面所示都是合法的实数。

2.1E5 (等于2.1 * 105);3.7E-2 (等于3.7 * 10-2);0.5E7 (等于0.5 * 107);-2.8E-2 (等于-2.8 *10-2)

而下面所示是不合法的实数。

345(无小数点);E7(阶码标志E之前无数字);-5 (无阶码标志);53.-E3(负号位置不对);

2.7E(无阶码)

标准C允许浮点数使用后缀,后缀为“f”或“F”即表示该数为浮点数,例如356f和356.是等价的。

2.6.2 变化的实型变量

因为实型变量有小数部分,所以它占用的内存比整型要多。在一般情况下,一个实型数据占用4字节内存,并且是以指数形式存在的。系统会把一个实型数据分为小数部分和指数部分,分别存储。实型变量还遵循如下规则。

● 小数部分占的位(bit)数越多,有效数字越多,精度越高;

● 指数部分占的位数越多,则能表示的数值范围越大。

1.实型变量的分类

实型变量分为单精度(float型)、双精度(double型)和长双精度(long double型)3类。

在Turbo C中单精度型占4个字节(32位)内存空间,其数值范围为3.4E-38~3.4E+38,只能提供7位有效数字。双精度型占8个字节(64位)内存空间,其数值范围为1.7E-308~1.7E+308,可提供16位有效数字。具体说明如表2-2所示。

表2-2 实型变量的类别

2.声明实型变量

实型变量定义的格式和书写规则与整型相同,只是将类型设置为“float”和“double”即可。例如下面的格式:

    float z,v;                                 //z,v为单精度实型量
    double a,b,c;                              //a,b,c为双精度实型量

3.实型数据的舍入误差

我:“实型数据有误差吗?”

KNOWALL:“实型变量是由有限的存储单元组成的,所以能提供的有效数字也是有限的,这样就会存在舍入的误差。为了避免误差产生,作为开发人员要避免将一个很大的数和一个很小的数进行运算处理,避免产生舍入的误差产生。”

我:“很大的数不能转换为很小的数?”

KNOWALL:“对,大的数取值范围会大,小的数取值范围会小,大转小就不可避免地会出错!下面我出个题来考一考你!”

测试3:将一个很大的数和一个很小的数进行加法运算。

解决思路:我的设计思路如下所示。

(1)定义两个实型变量a和b;

(2)对a和b分别赋值;

(3)对a和b实现相加,并输出结果。

编写文件“Error.c”,具体代码如下:

    #include <stdio.h>
    void main(){
      float a,b;                               //定义两个实型变量
      a=1222222e2;                             //赋值
      b=a+10;                                 //加一个小数
      printf("%f,%f\n",a,b);                  //输出结果
      getch();
    }

编写代码完毕后,编译运行后的效果如图2-17所示。

图2-17 执行结果界面

KNOWALL:“从图2-17所示的计算结果可以看出,当很大的实型数据和很小的实型数据相加时,其结果没有发生变化。结果中的前8位是准确的,而后几位是不准确的,把很小的数加在后面几位是没有任何意义的。”

我:“那怎么办?”

KNOWALL:“要尽量避免直接用一个较大的数除以一个较小的数。”

2.6.3 实型常量的类型

在C语言程序的实型常量运算时,C编译系统将实型常量作为双精度来处理。例如对一个已定义的实型变量f进行如下运算:

    f=2.45678 * 4523.65

系统将2.45678和4523.65按双精度数据存储(占64位)和运算,得到一个双精度的乘积,然后取前7位赋给实型变量f。这样做可以保证计算结果更精确,但是运算速度降低了。可以在数的后面加字母f或F(如1.65f,654.87F),这样编译系统就会按单精度(32位)处理。

一个实型常量可以赋给一个float型、double型或long double变量。根据变量的类型截取实型常量中相应的有效位数字。例如在下面的代码中,a已指定为单精度实型变量。

      float zz;
      a=111111.111;

由于float型变量只能接收7位有效数字,因此最后两位小数不起作用。如果zz改为double型,则能全部接收上述9位数字并存储在变量zz中。

2.7 字符型数据,人生如符号

9月10日,15:00,晴

午休醒来,大脑还昏昏沉沉的,仿佛做了一个很感性的梦,人生、符号在脑海中模模糊糊!

我:“字符型数据是用来表示字符的,我午休做了一个好奇怪的梦,脑海中有人生和符号之类的东西,这两种怎么会连在一起!”

KNOWALL:“对,字符型数据用来表示程序中的各种字符!你梦到了人生和符号,这很正常,其实人生如符号!例如逗号(,):逗号是一句话中间的停顿,在人生中,处处充满了逗号,人生的每个阶段,如童年、青年、中年、老年,其实都是一个个逗号;再例如问号(?):人生到处充满了问号,即使到了我们生命中的老年,我们仍然有不知道或不能理解的问题,所以问号在我们一生中是到处存在的,如图2-18所示。”

图2-18 人生如符号

C语言中的字符型数据分为字符常量和字符变量两种。字符常量在程序中其值不会变化,字符变量的值可变。

2.7.1 字符常量

字符常量是用单引号括起来的一个字符,例如'a'、'b'、'='、'+'、'?'等。C语言的字符常量有如下3个特点。

(1)字符常量只能用单引号括起来,不能用双引号或其他括号。

(2)字符常量只能是单个字符,不能是字符串。

(3)字符可以是字符集中任意字符。但数字被定义为字符型之后就不能参与数值运算。如'5'和5是不同的。'5'是字符常量,不能参与运算。

除了以上形式的字符常量外,C还允许使用以一个“\”开头的字符序列。例如,在printf函数中的“\n”表示是一个换行符,这是一种“控制字符”,在屏幕上是不能显示的。在程序中也无法用一个一般形式的字符表示,只能采用特殊形式来表示。

常用的以“\”开头的特殊字符如表2-3所示。

表2-3 常用转义字符说明

上表中列出的字符称为“转义字符”,意思是将反斜杠(\)后面的字符换成另外的意义。如'\n'中的“n”不代表字母n而是作为“换行”符。

上表中倒数第2行是用ASCII码(八进制数)表示一个字符,例如'\101'代表ASCII码(十进制数)为65的字符“A”。'\012'(十进制ASCII码为10)代表“换行”。用'\376'代表图形字符“■”。用上表中的方法可以表示任何可输出的字母字符、专用字符、图形字符和控制字符。请注意'\0'或'\000'是代表ASCII码为0的控制字符,即“空操作”字符,它将用在字符串中。

测试4:通过转义字符输出指定的文本字符。

解决思路:思路很简单,只需使用对应的转义字符即可。

编写文件tra.c,具体代码如下:

    #include <stdio.h>
    void main(){
    printf(" ab c\t de\rf\tg\n");
     printf("h\ti\b\bj k l mn");
     getch();
    }

我:“虽然区区几行代码,但是我看着迷糊,程序中也没有设置字符变量!”

KNOWALL:“在上述代码中,用printf函数直接输出双引号内的各个字符。其中第一个printf函数先在第一行左端开始输出“ ab c”,然后遇到“\t”,它的作用是“跳格”,即跳到下一个“制表位置”,在我们所用系统中一个“制表区”占8列。“下一制表位置”从第9列开始,故在第9~11列上输出“ de”。下面遇到“\r”,它代表“回车”(不换行),返回到本行最左端(第1列),输出字符“f”,然后遇到“\t”再使当前输出位置移到第9列,输出“g”。下面是“\n”,作用是“使当前位置移到下一行的开头”。第二个printf函数先在第1列输出字符“h”,后面的“\t”使当前位置跳到第9列,输出字母“i”,然后当前位置应移到下一列(第10列)准备输出下一个字符。下面遇到两个“\b”,“\b”的作用是“退一格”,因此“\b\b”的作用是使当前位置回退到第8列,接着输出字符“j k mn”。”

编写代码完毕后,编译运行后的效果如图2-20所示。

图2-20 执行结果界面

2.7.2 字符串常量

字符串常量是由一对双引号(“ ”)括起的字符序列,例如下面的都是合法的字符串常量。

    "CHINA";"C program";"$12.5"

我:“字符串常量和字符常量有区别吗?”

KNOWALL:“当然有!它们之间主要下区别如下。”

(1)字符常量由单引号(‘ ’)括起来,字符串常量由双引号(“ ”)括起来;

(2)字符常量只能是单个字符,字符串常量则可以含一个或多个字符;

(3)可以把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋予一个字符变量。在C语言中没有相应的字符串变量,这是与BASIC语言不同的。但是可以用一个字符数组来存放一个字符串常量。在数组一章内予以介绍;

(4)字符常量占一个字节的内存空间。字符串常量占的内存字节数等于字符串中字节数加1。增加的一个字节中存放字符"\0" (ASCII码为0),这是字符串结束的标志。

2.7.3 字符变量

字符变量用来存储字符常量,即单个字符。字符变量的类型说明符是char。字符变量类型定义的格式和书写规则都与整型变量相同,具体格式如下:

    char变量;

例如下面的代码定义了两个字符变量m和n:

    char m,n;

每个字符变量被分配一个字节的内存空间,因此只能存放一个字符。字符值是以ASCII码的形式存放在变量的内存单元之中的,例如x的十进制ASCII码是120,y的十进制ASCII码是121。下面代码对字符变量a和b赋予'x'和'y'值:

    a='x';
    b='y';

将一个字符常量放到一个字符变量中,并不是把字符本身放到内存单元中,而是将此字符的相应ASCII代码放到存储单元中。例如下面的代码:

    char a,b;
    ch1=’x’;
    ch2=’y’;

在上述代码中,字符“x”的ASCII码为120,字符“y”的ASCII码为121,将两个字符赋给字符变量ch1和ch2后。实际上是在ch1和ch2这两个单元内存放的是120和121的二进制代码:

    ch1=01111000;
    ch2=01111001;

所以,可以把a和b做整型量,即一个字符既可以以字符形式输出,也可以以整数形式输出。以字符形式输出时,需要预先将存储单元中的ASCII码转换为相应的字符,然后再输出;当以整数形式输出时,直接将ASCII码作为整数输出。

我:“我明白了,字符型数据和整型数据之间的转换十分简单和方便。它们之间可以相互赋值,并且可以直接进行运算。”

KNOWALL:“对!在输出时,字符型数据和整型数据是完全通用的,既可以以整数形式输出,也可以以字符形式输出。但是字符型数据只占1个字节,只能存放0~255范围内的整数。型量为二字节量,字符量为单字节量,当整型量按字符型量处理时,只有低八位字节参与处理。”

测试5:对字符变量和整型变量进行相互赋值,并将它们的运算结果输出。

解决思路:我的设计思路如下所示。

(1)分别声明整型变量和字符变量;

(2)分别将字符数据赋值给整型变量,将整型数据赋值给字符型变量;

(3)进行运算,并将运算结果输出。

编写文件Evaluation.c,具体代码如下:

    #include <stdio.h>
    void main(){
      int mm;                              //声明一个整型变量
      char nn;                             //声明一个字符变量
      mm='a';                              //将字符数据赋值给整型变量
      nn=100;                              //将整型数据赋值给字符型变量
      mm=mm-31;
      nn=nn-31;                            //字符数据与整型数据进行算术运算
      printf("%c,%c\n",mm,nn);             //以字符形式输出
      printf("%d,%d\n",mm,nn);            //以整数形式输出
      getch();
    }

编写代码完毕后,编译运行后的效果如图2-22所示。

图2-22 执行结果界面

2.8 从初始化变量谈人生初始化

9月10日,16:00,晴

我:“初始化变量就是对变量初始赋值!”

KNOWALL:“对,在程序中经常需要对变量赋初始值,以便使用变量。C语言程序中可有多种方法为变量提供初值,在此先介绍在定义变量的同时给变量赋以初值的方法,此种方法称为初始化。”

我:“计算机的初始化,仿佛基因注入,一旦初始化了计算机的程序,便决定了以后其他软件再嫁接上来的运行环境和效果。”

KNOWALL:“人生有太多的初始化!童年时的家庭教育便是人生的初始化,初恋是人的情感初始化,参加工作伊始的经历便完成了职业(事业)的初始化……如图2-13所示。”

在变量定义中初始赋值的一般格式如下:

    类型说明符 变量1= 值1,变量2= 值2,……;

例如下面的代码:

    int a=2;
    int b,c=3;
    float x=3.1,y=3f,z=0.25;
    char ch1='K',ch2='P';

在上述代码中,为各个变量进行了初始化赋值。但是在定义中不允许连续赋值,例如a=b=c=3是不合法的。看下面的一段代码:

    #include <stdio.h>
    main(){
      int a=b=c=3;
      b=a+c;
      printf("a=%d,b=%d,c=%d\n",a,b,c);
      getch();
    }

可以对上述代码进行如下修改:

    #include <stdio.h>
    main(){
      int a=1,b,c=5;
      b=a+c;
      printf("a=%d,b=%d,c=%d\n",a,b,c);
      getch();
    }

执行后将会输出指定结果,如图2-24所示。

图2-24 执行结果

2.9 整型、实型、字符型数据间的运算

9月10日,17:00,晴

我:“各种数据类型终于学完了!这些不同类型的数据能相互运算吗?”

KNOWALL:“当然可以,但是事先要转换成同一种数据类型然后才能运算。并且因为整型数据和实型数据可以进行运算,而且字符型数据可以和整型数据通用,所以整型、实型、字符型数据之间也是可以进行运算的。”

数据类型转换的方法有两种,一种是自动转换,另一种是强制转换。

2.9.1 自动转换

我:“自动转换是自动完成吗,不需要我们做什么?”

KNOWALL:“自动转换发生在不同数据类型的混合运算时,由编译系统自动完成。自动转换需要遵循如下5个原则。”

(1)如果参与运算量的类型不同,则先转换成同一类型,然后进行运算。

(2)转换按数据长度增加的方向进行,以保证精度不降低。例如int型和long型运算时,先把int量转成long型后再进行运算。

(3)所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。

(4)char型和short型参与运算时,必须先转换成int型。

(5)在赋值运算中,赋值号两边的数据类型不同时,赋值号右边的类型将转换为左边的类型。如果右边的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分按四舍五入原则向前舍入。

KNOWALL:“为加深你的理解,我给你画一个如图2-25所示的草图。”

图2-25 变量转换原则

我:“你这个草图太深奥了,我看不懂!”

KNOWALL:“横向箭头是运算时必定要进行的转换。例如char型数据必须转换为int型数据才可以运算,float型数据必须转换为double型数据才能运算。纵向箭头表示当运算对象的类型不同时转换的方向,例如char型数据和float型数据运算,是将char型数据转为double型数据后运算。”

注意

char型数据转为double型数据的过程是一次性的,无须中间过程,其他转换同样,不同类型的数据只有转换到图2-25中相交的结点时才能进行运算。

看下面的一段代码:

    #include <stdio.h>
    main(){
      float P=3.14;
      int s,r=3;
      s=r*r*P;
      printf("s=%d\n",s);
    }

在上述代码中,P为实型,s和r为整型。在执行面积计算语句“s=r*r*P”时,r和P都转换成double型计算,结果也为double型数值。但由于s为整型,故赋值结果仍为整型数值,舍去了小数部分。

2.9.2 强制转换

9月10日,17:30,多云

我:“强制转换就是强制将一个数据类型转换成其他类型吗?”

KNOWALL:“对,强制类型转换是通过类型转换运算来实现的,其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。”

C语言强制转换的格式如下:

    (类型说明符)  (表达式)

例如下面的转换:

    (float) a                      //把a转换为实型
    (int)(x+y)                     //把x+y的结果转换为整型

注意

在使用强制转换时应注意如下两个问题:

(1)类型说明符和表达式都必须加括号(单个变量可以不加括号),如把(int)(x+y)写成(int)x+y则成了把x转换成int型之后再与y相加。

(2)无论是强制转换或是自动转换,都只是为了本次运算的需要而对变量的数据长度进行的临时性转换,而不改变数据说明时对该变量定义的类型。

2.10 解决问题——规划你的人生算法,两弊相衡取其轻

无论你现在的地位如何,人的一生中总会面临很多选择!刚上大学时要选择自己的专业,大学毕业了还要选择自己的职业……正确的选择有助于个人的成长,不正确的选择也许会成为今后发展的障碍。

假如你是一个职场白领,在求职时会遇到选择的问题:究竟是选择高薪还是选择有前景的工作?

人们求职固然为了生存,但是很多人的第一个观念是工资多少。职业选择是人生的一个重要选择,这种头等大事应该看好的是发展前景。选择一个未来发展前景好的职业,以后的职业发展会十分顺畅,反之则可能在今后的发展中涉及职业转型,需要付出更多努力,增加更多隐性或显性成本。

选择是多种多样的。有两利取其重;有两害取其轻;也有孤注一掷的赌博式选择……一般来说,选择是痛苦的,因为选择就意味着放弃。看下面这个故事:

有一个傻大姐,媒婆替她说亲,问她:“东家人丑而家富,西家人俊而家贫。你选择哪一个?”傻大姐毫不含糊地答道:“那就西家卧而东家食吧。”

傻大姐真是傻得可爱,企图利益均沾,却不知道鱼与熊掌不可兼得。

当鸟儿选择在两翼上系上黄金,就意味着它放弃展翅高飞;选择云天搏击,就意味着放弃身外的负累。智者曰:“两弊相衡取其轻,两利相权取其重。”——关键就在于你看重什么。

所以说,选择的学问,同时也是一门放弃的学问。学会放弃,才能卸下人生的种种包袱,轻装上阵,从容地等待生活的转机,踏过人生的风风雨雨;懂得放弃,才拥有一份成熟,才会活得更加充实、坦然和轻松。能够放弃,就已经拥有了当机立断的果敢以及顾全大局的胆识。

人生总是要面临这样那样的选择。犹豫不决,就必然会错失良机,后悔莫及。人生之路是不可逆的,也就是说不可能重新选择。即使不满意,也无可奈何,只有硬着头皮走(继续选择)下去。

后悔是什么?后悔就是以为当初可以有更好的选择!

选择当及时,千鸟在林,不如一鸟在手。只有参透选择和放弃玄机的人,才能彻悟人生,才能笑看人生,拥有海阔天空的人生境界。

2.11 我的总结

本章先讲解了编程语言的灵魂算法,然后讲解了C语言的数据类型。如表2-4所示是我的学习时间进度表。

表2-4 时间进度表