C语言(Head First C)-5_1:使用多个源文件:数据类型和使用头文件声明函数

来源:互联网 发布:英语作文翻译软件 编辑:程序博客网 时间:2024/06/05 21:53

该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

 5_1 使用多个源文件:

 数据类型和头文件;


 大程序不等于大源文件:

 只有一个源文件的话,维护耗时且困难;如何把源文件分解为易于管理的小模块,然后合成一个大程序,正是本章的内容;

 同时还将了解数据类型的更多细节,并结识一个新朋友make;

 

 数据类型:

 C语言可以处理不同的数据类型:字符、整数、浮点数(分为普通浮点数和科学计算用的高精度浮点数);

 浮点型:float double;(包含小数点)

 整型:short int long char;(你没看错,char以字符编码保存,换句话说,他们也是数字)

 

 简明数据类型指南:

 char:

 字符在计算机的存储器中以字符编码的形式保存,字符编码就是一个数字,在计算机看来,A和65完全一样;(65是A的ASCII码)

 

 int:

 存储整数,不同计算机中的int大小不同,但至少要有16位,一般而言,int可以保存几万以内的数字;

 

 short:

 有时想节省一点空间,存储小一点的证书,可以用short,它一般只有int一半的大小;

 

 long:

 保存一个很大的数字,一些计算机中long是int的两倍,可以保存几十亿以内的数字;大部分计算机的long和int一样大,因为这些计算机中int本身就很大;

 

 float:

 保存浮点数的基本数据类型;

 

 double:

 表示很精确的浮点数;double比float多占一倍的空间,可以保存更大、更精确的数字;

 

 勿以杯小盛大物:

 赋值时要保证值的类型与保存他的变量类型相匹配;

 不同数据类型的大小不同,千万别让值的大小超过变量;(short < int < long)

 

 可以用int装short,反过来不行(编译器可能不会报错,实际会让值面目全非);

 可以使用%hi来格式化short类型值;

 

 使用类型转换把float值存进整数变量:

 两个整数相除,结果是一个四舍五入的整数;如果希望结果是浮点数,可以使用类型转换 临时转换数值的类型;

 float z = (float)x;//(float)会把int值转换为float值;

 编译器如果发现浮点数与整数的运算,会自动把整数进行类型转换,以减少代码中类型装换的次数;

 

 可以在数据类型之前加几个关键字,来改变数值的意义:

 unsigned:

 修饰非负数的值,由于无需记录负数,无符号数有更多的位可以使用,可以保存更大的数(0~max,max是int可以保存最大值的两倍左右);

 还有signed关键字,没见过对吧,因为所有数据类型默认都是有符号的;

 unsigned char c;//保存0-255的数;(2^8-1)

 

 long:

 没错,你可以在数据类型前加long,让他变长;long int;long long;还可以对浮点数用long,long double;

 只有C99和C11标准支持long long;

 

 示例:

 帮助餐厅服务员更好滴进行服务,代码自动计算总账,并为每笔消费收取消费税;

 (Code5_1)

/* * 数据类型 */#include <stdio.h>float total = 0.0;short count = 0;short tax_persent = 6;float add_with_tax(float f){    float tax_rate = 1 + tax_persent/100.0;    total = total + (f * tax_rate);    count = count + 1;    return total;}int main() {    float val;    printf("Price of items:");    while (scanf("%f",&val) == 1) {        printf("Total so far:%.2f\n",add_with_tax(val));        printf("Price of items:");    }        printf("\nFinal total:%.2f\n",total);    printf("Number of item:%hi\n",count);    return 0;}

 log:

 Price of items:5

 Total so far:5.30

 Price of items:6

 Total so far:11.66

 Price of items:112

 Total so far:130.38

 Price of items:7

 Total so far:137.80

 Price of items:423

 Total so far:586.18

 Price of items:4566

 Total so far:5426.14

 Price of items:23

 Total so far:5450.52

 Price of items:

 wer

 

 Final total:5450.52

 Number of item:7

 

 聚焦数据类型大小:

 不同平台数据类型的大小不同,那怎么知道int或double占多少字节?

 C标准库提供了这些细节:下面这段代码,告诉你int与float的大小;

 (Code5_2)

/* * int float的大小 */#include <stdio.h>#include <limits.h>//含有表示整数大小的值(int 和 char)#include <float.h>//含有表示浮点数大小的值(float 和 double)int main() {        printf("int:");    printf("The value of INT_MAX is %i\n",INT_MAX);    printf("The value of INT_MIN is %i\n",INT_MIN);    printf("An int takes %lu bytes\n",sizeof(int));        printf("char:");    printf("The value of CHAR_MAX is %i\n",CHAR_MAX);    printf("The value of CHAR_MIN is %i\n",CHAR_MIN);    printf("An int takes %lu bytes\n",sizeof(char));        printf("short:");    printf("The value of SHRT_MAX is %i\n",SHRT_MAX);    printf("The value of SHRT_MIN is %i\n",SHRT_MIN);    printf("An int takes %lu bytes\n",sizeof(short));        printf("long:");    printf("The value of LONG_MAX is %li\n",LONG_MAX);    printf("The value of LONG_MIN is %li\n",LONG_MIN);    printf("An int takes %lu bytes\n",sizeof(long));        printf("float:");    printf("The value of FLT_MAX is %f\n",FLT_MAX);    printf("The value of FLT_MIN is %f\n",FLT_MIN);        printf("An int takes %lu bytes\n",sizeof(float));            return 0;}

 log:

 int:The value of INT_MAX is 2147483647

 The value of INT_MIN is -2147483648

 An int takes 4 bytes

 char:The value of CHAR_MAX is 127

 The value of CHAR_MIN is -128

 An int takes 1 bytes

 short:The value of SHRT_MAX is 32767

 The value of SHRT_MIN is -32768

 An int takes 2 bytes

 long:The value of LONG_MAX is 9223372036854775807

 The value of LONG_MIN is -9223372036854775808

 An int takes 8 bytes

 float:The value of FLT_MAX is 340282346638528859811704183484516925440.000000

 The value of FLT_MIN is 0.000000

 An int takes 4 bytes

 

 不同计算机上的结果可能不同;如果想知道double可以使用DBL替换INT即可;相关的常量值可以百度搜索相应的头文件:

 limits.h:含有表示整数大小的值(int和char)

 float.h:含有表示浮点数大小的值(float和double)

 

 为了适应硬件,C语言在不同操作系统与处理器上使用不同的数据类型的大小;(C语言并没有指定数据类型的大小)

 8位、64位中的位数既可以代表CPU指令的长度,也可以代表CPU依次从从存储器读取数据的大小;实际上,位数是计算机能够处理的数值长度;如果一台计算机能处理32位数值,就会把基本数据类型(如int)的大小设置成32位;

 

 函数声明:

 这里对调用的函数声明位置做一下调整:

 Code5_1代码中将函数声明移到main()函数之后,我们重新编译运行;

 log:

 5_1-numeric.c:18:38: warning: implicit declaration of function 'add_with_tax' is

 invalid in C99 [-Wimplicit-function-declaration]

 printf("Total so far:%.2f\n",add_with_tax(val));

 ^

 5_1-numeric.c:18:38: warning: format specifies type 'double' but the argument

 has type 'int' [-Wformat]

 printf("Total so far:%.2f\n",add_with_tax(val));

 ~~~~    ^~~~~~~~~~~~~~~~~

 %.2d

 5_1-numeric.c:26:7: error: conflicting types for 'add_with_tax'

 float add_with_tax(float f){

 ^

 5_1-numeric.c:18:38: note: previous implicit declaration is here

 printf("Total so far:%.2f\n",add_with_tax(val));

 ^

 2 warnings and 1 error generated.

 

 显然,编译器报错了;

 

 编译器做了什么?

 1)printf("Total so far:%.2f\n",add_with_tax(val));

 编译器看到了一个不认识的函数调用,先记下,随后在文件中查找该函数;

 2)编译器需要知道函数的返回类型,但现在还不知道,所以只好假设它返回int;

 3)等编译器看到实际的函数,返回了error: conflicting types for 'add_with_tax'错误;因为编译器认为有两个同名的函数,一个是文件中的函数,一个是编译器假设返回int的那个;

 

 如果把函数的位置放到main()函数调用ta之前定义,编译器就不用假设未知函数的返回类型了;但如果总是以特定的顺序定义函数,会有一些问题:

 1)调整函数的顺序很痛苦:如果新添加一个函数中调用了已有函数,那就需要把被调用函数的位置提前;

 最后又一个两全其美的方法,既不用交换代码顺序,又能让编译器高兴;

 2)在某些场景中,没有正确的顺序:比如两个相互递归调用的函数,func1中调用func2,func2中调用func1,这样总会有一个函数在定义前被调用;

 

 那么有什么办法呢?

 

 声明与定义分离:

 如果编译器一开始就知道函数的返回类型,就不用稍后再找了;为防止编译器假设函数的返回类型,我们可以显示的告诉他;

 告诉编译器函数会返回什么类型的语句就叫函数声明;

 如:float add_with_tax(float f);

 

 声明只是一个函数签名:一条包含函数名、形参类型与返回类型的记录;

 一旦声明函数,编译器就不需要假设了,可以先调用,再定义;

 

 如果代码中有很多函数,又不想管他们的顺序,就可以在代码开头列出函数声明;

 甚至,可以把这些声明拿到代码外,放到一个头文件中,你已经用头文件包含过C标准库中的代码;

 #include <stdio.h>包含一个叫stdio.h的头文件的内容;

 

 创建自己的头文件:

 两件事;

 1)创建一个扩展名为5_3-totaller.h的文件;并在其中写上函数声明;

 不用写main()函数,反正也没有函数调用他;

 (Code5_3)

float add_with_tax(float f);    

 2)在主代码中包含头文件;使用include "5_3-totaller.h"

 我们创建一个主代码文件;

 (Code5_4)

/* * 数据类型 */#include <stdio.h>#include "5_3-totaller.h"float total = 0.0;short count = 0;short tax_persent = 6;int main() {    float val;    printf("Price of items:");    while (scanf("%f",&val) == 1) {        printf("Total so far:%.2f\n",add_with_tax(val));        printf("Price of items:");    }        printf("\nFinal total:%.2f\n",total);    printf("Number of item:%hi\n",count);    return 0;}float add_with_tax(float f){    float tax_rate = 1 + tax_persent/100.0;    total = total + (f * tax_rate);    count = count + 1;    return total;}

 头文件的名字使用双引号括起来,注意不是尖括号;

 区别在于:

 当编译器看到尖括号,就会到标准库代码所在目录查找头文件,但现在你的头文件和.c文件在同一目录下,用引号把文件名括起来,编译器就会在本地查找文件;

 当编译器在代码中,读到#include,就会读取头文件中的内容,仿佛他们本来就在代码中;

 本地头文件也可以带有目录名,但通常会把它和c文件放在相同目录下;

 

 把声明放到一个独立的头文件中有两个有点:一个就是代码变短了,第二个稍后会看到;

 #include 是预处理命令;

 

 现在我们重新编译运行一下上边这段主代码:代码正常运行了;

 

 小结:

 1)函数定义在调用之后,需要提前声明;

 2)如果未声明的函数返回值是int行,可编译通过,但会收到一条警告;

 3)如果没有声明返回值不为int的函数,编译无法通过;

 4)只有使用gcc编译器的-std=c99选项编译后才会有该警告;

 

 预处理:

 预处理是把C源代码转化为可执行文件的第一个阶段;预处理会在正式编译开始之前修改代码,“创建”一个新的源文件;以我们的代码为例,预处理会读取头文件中的内容,插入主文件;这里的创建并不是真正的创建一个文件,为了提高编译效率,编译器通常会用到管道,在两个阶段之间发送数据;

 gcc会预处理和编译代码;

 

 区别用尖括号或引号括起来的头文件:

 这是由于编译器的工作方式决定的;通常,引号表示以相对路径查找头文件,如果不加目录名,只包含一个文件名,编译器就会在当前目录下查找头文件;如果使用尖括号,编译器就会以绝对路径查找头文件;gcc知道标准库的头文件保存的目录;

 我们也可以创建自己的库,后续会介绍;

 

 要点:

 -如果编译器发现你调用了一个他没见过的函数,就会假设这个函数返回int;

 -所以如果想在定义函数前就调用它,就可能出问题;

 -函数声明在定义函数前就告诉编译器函数长什么样子;

 -如果在源代码顶端声明了函数,编译器就知道函数返回什么类型;

 -函数声明通常放在头文件中;

 -可以用#include让编译器从头文件中读取内容;

 -编译器会把包含进来的代码看成源文件的一部分;



阅读全文
0 0
原创粉丝点击