该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
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让编译器从头文件中读取内容;
-编译器会把包含进来的代码看成源文件的一部分;