一.基本数据类型
以下所写的占用的存储空间在不同编译器中可能不同,标注的只是通常的情况
//数据类型关键字:
int:整型 signed(C90中新增):有符号的
long:增长 void(C90中新增):空值
short:缩短 _Bool(C99中新增):布尔值
unsigned:无符号的(仅有正数) _Complex(C99中新增):复数
char:字符 _Imaginary(C99中新增):虚数
float:(单精度)浮点型
double:双精度浮点型
1.整型:
用于储存整数,分为:
1.短整型(short int或short):占用2B(不能多于int)
2.整型(int):占用4B
3.长整型(long int或long):占用4B(不能少于int)
4.16进制或8进制整数:
①在前面加0x或0X表示16进制数,如0x10表示10进制下的16
②在前面加0表示8进制数,如010表示10进制下的8
5.long long int或long long:C99加入;占用至少8B(不能少于long int)
6.unsigned int或unsigned:无符号的整型
可以表示比int允许的更大的数:如16位unsigned取值范围是0~65535,而int是-32768~32767
7.unsigned long int或unsigned long:C90加入
8.unsigned short int或unsigned short:C90加入
9.unsigned long long int或unsigned long long:C99加入
10.在任何有符号类型前加signed,可强调有符号的意图:如short与signed short相同
11._Bool:布尔值,即true和false;实际上也是1种整数类型,用0表示false,1表示true,但原则上仅占用1位
参见 C语言细节.流程控制.2 部分
注意:
1.有些编译器无法识别5/7/8/9
2.优先使用unsigned类型以表示更大的数
3.较大的类型会降低运算速度,尽量避免使用
- 整型存储方式:
2.浮点型:
用于储存小数,分为:
1.单精度浮点数(float):占用4B,7位有效数字
2.双精度浮点数(double):占用8B,15~16位有效数字
3.long double:精度不低于double
- 存储问题:
1.浮点数的存储可能存在误差
比如float x=6.234,实际存储的可能是6.239999
因此,判断float类型的变量x的值是否为0的标准方法是:
if (abs(x)<= 1e-6)//abs()表示取绝对值
printf("是");
else
printf("不是");
判断double类型的变量y的值是否为0的标准方法是:
if (abs(y)<= 1e-15)
printf("是");
else
printf("不是");
//float的精度误差为1e-6,double的精度误差为1e-15
2.对于一些算术运算(如2个大数相减),浮点数损失的精度更多
3.浮点数运算比整数运算慢,虽然现在这个差距已经缩小了
- 浮点型的存储方式:
注:在计算机内部使用的是二进制和2的幂进行存储,而不是十进制和10的幂
3.字符型:
字符(char):用于储存单个字符,使用' '括起,占用1B
#注意:
1.C语言中没有可以直接存储多个字符的数据类型,需要通过字符数组来完成该功能(参见 C语言基础.数组.六 部分)
2.char类型不得使用" "扩起,因为这样内部至少有2个字符(如"A"表示"A\0")
不过好像可以通过编译,但警告如下:
[Warning] initialization makes integer from pointer without a cast
而且下述命令打印不出来变量值:
char m="a";
printf("%c",m);//结果: (没错,什么都没有)
3.''不合法,但' '合法
4.字符变量实际上存储的是该字符相应的ASCII码,因此可以像整数一样进行运算
char ch;
scanf("%c",&ch);//输入:s
//char ch=getchar();//相当于上面2行
putchar(ch+1);//结果:t
printf("\n");
putchar(ch/2);//结果:9
putchar('\n');
putchar(ch-ch/10);//结果:h
printf("\n");
printf("%c\n",ch-2);//结果:q
二.复合数据类型
1.结构体:
参见 C语言基础.结构体 部分
2.枚举(Enumerated Type)
(1)什么是枚举:
定义:列出某个有穷集的所有成员
即:把某个事物所有可能的取值全部列举出来
(2)语法:
//定义1个枚举:这里是定义了1个数据类型(或模板),而不是定义了1个变量
enum <enum_name> {
<name0>[=<val0>],<name1>[=<val1>]...
//注意:结尾没有分号(;)
};//注意:这里有分号(;)
//参数说明:
enum_name:枚举的名称
name:该枚举所有可能的成员的名称(这些符号常量称为"枚举符"(Enumerator))
//这里的名称可以是任意值(如为Monday),不一定是char/int...等C语言中的数据类型
val:该枚举所有可能的成员的值
//本质上存储和处理的是这些值,但赋值时不能用相应的值来代替成员名称
//这些可能的值默认会被从0开始按顺序编号(val0为0,val1为1...)
//指定某成员的值为x,则之后的值会从x+1开始按顺序编号,直到下1个被指定了值的成员
//定义1个枚举变量:这里才定义了1个变量
enum <enum_name> <evar>[=<val>];
//参数说明:enum_name同上
evar:变量名
val:该变量的值
//此处的值必需在定义枚举时列举的值(val0,val1...)中
//而且不能使用该值的编号代替该值
//实例:
#include <stdio.h>
enum WeekDay {
Monday,Tuesday,Wednesday,Thursday,Friday=10,Saturday,Sunday
};
void f(enum WeekDay i) {
switch (i) {
case 0:printf("Monday\n");break;
case 1:printf("Tuesday\n");break;
case 2:printf("Wednesday\n");break;
case 3:printf("Thursday\n");break;
case 10:printf("Friday\n");break;
case 11:printf("Saturday\n");break;
case 12:printf("Sunday\n");break;//结果:Sunday
}
}
int main(void) {
enum WeekDay day1=Wednesday,day2=Sunday;
printf("%d\n",day1);//结果:2//因为Wednesday是WeekDay中的第3个选项
//把枚举变量当作int输出,实际上输出的是枚举变量的值的编号
printf("%d\n",)//结果:12
f(day2);//day2实际上是按其值(Sunday)的编号(12)来被处理的
return 0;
}
(3)枚举的优缺点:
优点:代码更安全;提高了代码的可读性
缺点:书写麻烦
(4)自增/减:
C语言允许对枚举变量使用自增/减运算符:
//不过C++不允许,所以如果希望兼容C++,就不能使用自增/减运算符
#include <stdio.h>
enum colors {
red,yellow,green,black
};
int main(void) {
enum colors color;
char * acolors[]={"red","yellow","green","black"};
for (color=red;color<black;color++) {
printf("%s ",acolors[color]);
}
return 0;
}
//结果:
red yellow green
3.联合
参见 C语言细节.其他数据类型.一 部分
三.变量
1.什么是变量:
变量是那些在程序运行过程中可以改变的量
变量指向某块内存空间,这块内存空间中保存有该变量存储的数据
也就是说,变量的本质就是内存中的1段存储空间
程序一旦运行完毕,为其中所有变量分配的内存空间都会被释放掉
释放指这块内存空间可被分配给其他变量,而非清除内存中的遗留数据
变量类型同数据类型
2.变量名:
变量名是1种标识符
//标识符命名规则:
1.只能由字母/数字/下划线(_)组成,数字不能在首位
//在C99/C11中根据通用字符名(UCN)机制添加了扩展字符集,其中包含了更多字符
2.不能用C语言的关键字作为变量名
3.但编译器只识别标识符名的前63个字符;外部标识符只允许31个字符
//如果2个标识符名在63个字符后才有区别,标准并未规定,结果取决于编译器
//C90仅允许6个字符;旧式编译器通常只允许8个字符
4.区分大小写
5.标识符名应尽量有意义(非强制)
//注意:
1个变量名在1个程序中只能被定义1次:
int a=45;
int a=9;
//报错:
[Error] redefinition of 'a'
[Note] previous definition of 'a' was here
3.声明变量:
//注意:每条命令后面都需要加";"
//声明变量:声明为变量创建并标记内存空间
<type> <var>[=<val>];
//这种只存储单个值的变量有时称为"标量变量"(Scalar Variable)
//更详细的规则参见 C语言细节.其他数据类型.四 部分
//参数说明:
type:指定数据类型
var:指定变量名
val:指定变量值(这个值本身是1个常量)
//也可以把创建和赋值分开写:
<type> <var>;
<var>=<val>;
//说明:
1.C99允许在任何位置定义变量
int price=0;
printf("请输入金额(元):");
scanf("%d",&price);
int change=100-price;
printf("找零为%d元\n",change);
2.ANSI C要求必须在程序开头定义所有变量
int price=0;
int change=0;
printf("请输入金额(元):");
scanf("%d",&price);
change=100-price;
printf("找零为%d元\n",change);
3.变量必须先被定义,然后才能使用,如果定义位置在使用位置后,会找不到该变量
4.1个变量名不能在1个作用域内只能使用1次
//实例:
int i=3;
等价于:int i;i=3;
int i,j;i=3;j=5;
等价于:int i;int j;i=3;j=5;
int i,j=3;
等价于:int i;int j;j=3;
int i=3,j=5;
等价于:int i;int j;i=3;j=5;
int i,j;i=j=5
等价于:int i;int j;i=5;j=5
4.变量的初始化:
初始化的意思就是赋初值,以覆盖内存中的垃圾值(即上1个占用这块内存空间的变量的遗留值)
如果不初始化,变量的值是0(为int)/空格(为char)/0.000000(为float)或为垃圾值或自动赋1个很大的值
在C语言中,初始化是在声明变量时完成的,如:
int a=11;
int b=1,c=2;
int d,e=0;//注意:这里仅初始化了e
四.常量
常量是固定值,在程序执行期间不会改变,又叫字面量
常量就像是常规的变量,不过其值在定义后不能修改
常量分为整数,浮点数,字符
1.表示方法:
整数:
10进制:普通写法,如11
16进制:前面加0x/0X,如0x1F表示31
8进制:前面加0(加数字零,不是O),如010表示8
会按int-unsigned-long-unsigned long-long long-unsigned long long尝试存储
要指定作为某类型存储,可在值最后加后缀(10/8/16进制下均可),如12L/12ULL
浮点数:
普通写法:如94.23
科学计数法:如1.23e3表示1230,3.1e-1表示0.31
可在值最后加后缀以指定类型,默认是double,加L/l是long double,加F/f是float
字符:
//所有用" "扩起的内容默认都会被在结尾补上1个结束符'\0'
//在创建常量时,二者的type都是char
单个字符:用' '扩起,如'a'
注意:'ab'错误
多个字符(字符串;str):用" "扩起,如"ale"
注意:"a"正确,表示"a\0"
注意:输入不符合数据类型的值不一定报错,如:int a=1.2只会导致警告
2.声明常量:
//方法1:将<var>定义成1个符号,此时<var>只是<val>的别名
常量名通常全部大写(非强制)
这样定义的常量称为"符号常量"(Symbolic Constant)或"明示常量"(Manifest Constant)或"宏常量"(Macro-Defined Constant)
程序中所有的<var>都会在编译时被替换为<val>,该过程称为"编译时替换"(Compile-Time Substitution)
#define <var> <val>;
//参数说明:
var:指定常量名
val:指定常量值(这个值本身也是1个常量,即字面量)
//可以是整数/浮点数/字符/字符串/表达式
//实例:
#define Pi 3.1415926f;
//方法2:将<var>定义成1个变量并告诉编译器其值固定,如果试图修改其值,则编译时会报错
这种本质上还是变量,称为"常变量";常变量名统称全部小写
const <type> <var> <val>;
//参数说明:
type:指定数据类型
var:指定常量名
val:指定常量值
//实例:
const float pi 3.1415926f;
3.常量/变量的二进制代码:
·整数以补码的形式存储,实数按IEEE754标准存储
·字符按ASCII转换成整数,转换后的整数再以补码形式存储
五.数据类型的转换
1.不同类型数据间的相互赋值:
1.字符和整数间可按ASCII互相转换
2.整数间,浮点数间,整数和浮点数间也可以互相转换,不过存在溢出的风险
通常,在语句和表达式中应使用类型相同的变量和常量
但是,如果使用混合类型,C语言会进行自动类型转换,但这有一定的危险性
1.当类型转换出现在表达式时,unsigned或signed的char/short会被自动转换成int,如有必要则
会被转换成unsigned int(如果short与int大小相同,unsigned short就比int大,这种情况下,
unsigned short会被转换成unsigned int);在K&R那时的C中,f1oat会被自动转换成double(目
前的C则不是);由于都是从较小类型转换为较大类型,所以这些转换被称为升级(Promotion)
2.涉及2种类型的运算,2个值会被分别转换成2种类型的更高级别
3.类型的级别从高至低依次是long double,double,float,unsigned long long,long long
unsigned long,long,unsigned int,int;例外是,当long和int大小相同时,unsigned int比
long的级别高;之所以没有列出short和char,是因为它们已经被升级到int/unsigned int
4.在赋值表达式语句中,计算结果会被转换成被赋值变量的类型,这个过程可能导致类型升级或降
级(Demotion);降级就是指转换成更低级别的类型
5.作为函数参数传递时,char/short被转换成int,float被转换成double
int i=45;
long j=102345;
i=j;
printf("%ld,%d\n",i,j);//结果:102345,102345
float x=6.6;
double y=8.8;
printf("%f,%lf\n",x,y);//结果:6.600000,8.800000
int a=45;
int b=2147483649;//会发生溢出
printf("%d,%d\n",a,b);//结果:45,-2147483647
char m="a";
m=a;
printf("%c",m);//结果:-
char ch='A';
printf("%d\n",ch);//结果:65
char c=65;
printf("%c\n",c);//结果:A
注意:升级通常不会有问题,但降级可能导致溢出
//
如果待转换的值和目标类型不符,结果将取决于转换涉及的类型,规则如下:
1.目标类型是无符号整型且待赋的值是整数时,额外的位将被忽略
如,如果目标类型是8位unsigned char,待赋的值是原始值求模256
2.如果目标类型是有符号整型且待赋的值是整数,结果因实现而异
3.如果目标类型是整型且待赋的值是浮点数,该行为是未定义的
4.当浮点类型被降级为整数类型时,原来的浮点值会被截断
如23.12和23.99都会被截断为23,-23.5会被截断为-23
2.强制类型转换运算符(Cast Operator):
//语法:
(<type>)(<var>)
//参数说明:
type:指定要转换成什么类型
var:要转换的变量;可为变量名或变量值
//实例:
printf("%c\n",(char)(39));//结果:'
printf("%d\n",(int)('A'));//结果:65//即int→char按ASCII转换
printf("%d\n",(int)(3.7+1.2));//结果:4//即float→int会截断到整数位
printf("%f\n",(float)(3));//结果:3.000000//即int→float会补0
printf("%c\n",(char)(65.78));//结果:A//即float→char会先截断再按ASCII转换(相当于(char)((int)(<float>)))
int i;
float sum=0.0;
for (i=1;i<=100;i++) {
printf("%d\n",1/i);//结果:1 0 ... 0
printf("%f\n",1/(float)(i));//结果:1.000000 0.500000 ... 0.010000
printf("%f\n",(float)/(1/i));//结果:1.000000 0.000000 ... 0.000000
sum+=1/(float)(i);
//相当于:sum+=1.0/i;
printf("%f\n",sum);//结果:1.000000 1.500000 ... 5.187378
}
printf("%f\n",sum);//结果:5.187378
六.变量的作用域和存储方式
1.变量按作用域分类:
①全局变量:在所有函数外部定义的变量
作用域默认是从定义的位置到程序结尾
②局部变量:在函数内部定义的变量和函数的形参
作用域只限于当前语句块(不包括上级语句块,也不包括下级语句块)
在main()中定义的变量也是局部变量;main()中也不能使用其它函数中定义的变量—main()与其它函数平权
可以在不同函数中使用相同的变量名,它们表示不同的数据,分配不同的内存,互不干扰
实参给形参传值的过程也就是给局部变量赋值的过程
如果局部变量和全局变量同名,全局变量在该作用域中会被局部变量覆盖
//实例:
#include <stdio.h>
#include <math.h>
void qqq(int i);
int j=33,r=22222;//全局变量
int main(void) {
int i=12;//局部变量
qqq(i);//传入局部变量
return 0;
}
void qqq(i) {//局部变量
printf("%d\n",i);//结果:12//使用局部变量
//int j=2233;//加上这行结果是2233//局部变量
void ppp(j) {//局部变量
printf("%d\n",j);//结果:33//使用局部变量
}
ppp(j);//传入全局变量;如果加上int j=2233那行,则此次传入的是局部变量
}
void ttt(int k) {//局部变量
printf("%d,%d\n",k,r);//使用局部变量和全局变量
}
2.变量按存储方式分类:
①静态变量
②自动变量
③寄存器变量
七.关键字与保留标识符
1.关键字:
以下既包括C89中的关键字,也包括C90/C99/C11中新增的关键字
auto | break | case | char | const | continue |
---|---|---|---|---|---|
default | do | double | else | enum | extern |
float | for | goto | if | inline | int |
long | register | restrict | return | short | signed |
sizeof | static | struct | switch | typedef | union |
unsigned | void | volatile | while | _Alignas | _Alignof |
_Atomic | _Bool | _Complex | _Generic | _Imaginary | _Noreturn |
_Static_assert | _Thread_local |
//使用关键字作为标识符会报错:
#include <stdio.h>
int main(void) {
int return=11;
return 0;
}
//报错:[Error] expected identifier or '(' before 'return'
2.保留标识符(Reserved Identifier):
还有一些保留标识符,已被C语言使用或保留了使用权,如printf
使用保留标识符作为标识符不会直接报错,但可能导致其他问题
//实例:
#include <stdio.h>
int main(void) {
int printf=11;
printf("AAA\n");//注释掉该行即可编译通过
return 0;
}
//报错:[Error] called object 'printf' is not a function or function pointer