变量和数据类型
1.变量
变量------一个有名字的,具有具体属性的一个存储单元。你可以将变量直接就理解为内存。C语言的规则是:变量必须先定义才能使用。对变量的定义其实就是请求计算机,让计算机将内存中的某个单元分配给你定义的变量使用。
int a;//在内存中找一块区域,命名为 a,用它来存放整数。
a = 100;//赋值是指把数据放到内存的过程,这里把100放到了一块叫做 a 的内存区域。
//先定义变量再初始化
int b; //变量定义
b = 200; //给变量赋值,因为是第一次赋值,也称变量的初始化,或者赋初值。
//定义的同时进行初始化
int b = 200;
int c = 300;
c = 30;//第二次赋值,会把第一次的数据覆盖(擦除)掉
//连续定义多个变量
int a, b, c;
int m = 10, n = 20;
2.数据类型
数据是放在内存中的,变量是给这块内存起的名字,有了变量就可以找到并使用这份数据。而数据类型用来说明数据的类型,确定了数据的解释方式,让计算机和程序员不会产生歧义。
C 中的类型可分为以下几种:
序号 | 类型与描述 |
---|---|
1 |
基本类型: 它们是算术类型,包括两种类型:整数类型和浮点类型。 |
2 |
枚举类型: 它们也是算术类型,被用来定义在程序中只能赋予其一定的离散整数值的变量。 |
3 |
void 类型: 类型说明符 void 表明没有可用的值。 |
4 |
派生类型: 它们包括:指针类型、数组类型、结构类型、共用体类型和函数类型。其中数组类型和结构类型统称为聚合类型,函数的类型指的是函数返回值的类型 |
整数类型
类型 | 存储大小 | 值范围 |
---|---|---|
char | 1 字节 | -128 到 127 或 0 到 255 |
unsigned char | 1 字节 | 0 到 255 |
signed char | 1 字节 | -128 到 127 |
int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647,即 -~(-1) |
unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295,即0~(-1) |
short | 2 字节 | -32,768 到 32,767,即 -~(-1) |
unsigned short | 2 字节 | 0 到 65,535,即 0~(-1) |
long | 4 字节 | -2,147,483,648 到 2,147,483,647,即 -~(-1) |
unsigned long | 4 字节 | 0 到 4,294,967,295,即0~(-1) |
浮点类型
类型 | 存储大小 | 值范围 | 精度 |
---|---|---|---|
float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位小数 |
double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位小数 |
long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位小数 |
总结:a、变量名不仅仅是为数据起了一个好记的名字,还告诉我们数据存储在哪里,使用数据时,只要提供变量名即可;而数据类型则指明了数据的长度和处理方式。所以诸如int n;
、char c;
、float money;
这样的形式就确定了数据在内存中的所有要素。
b、除了C语言,Java、C++、C#等在定义变量时也必须指明数据类型,这样的编程语言称为强类型语言。而PHP、JavaScript等在定义变量时不必指明数据类型,编译系统会自动推演,这样的编程语言称为弱类型语言。
c、强类型语言一旦确定了数据类型,就不能再赋给其他类型的数据,除非对数据类型进行转换。弱类型语言没有这种限制,一个变量,可以先赋给一个整数,然后再赋给一个字符串。
3.格式控制符
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 100;
char b = '@'; //字符用单引号包围,字符串用双引号包围
float c = 10.38;
printf("a=%d,b=%c,c=%f \n",a,b,c);
}
//结果:a=100,[email protected],c=10.380000
//%d 输出整数,%s 输出字符串,那么 %ds 输出什么呢?
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 100;
printf("a=%ds \n",a);
}
//结果:a=100s,%d被替换成了变量 a 的值,而s没有变,原样输出了。这是因为, %d才是格式控制符,%ds在一起没有意义,s仅仅是跟在%d后面的一个普通字符,所以会原样输出。
%d
称为格式控制符,它指明了以何种形式输出数据。格式控制符均以 %
开头,后跟其他字符。%d 表示以十进制形式输出一个整数。除了 %d,printf 支持更多的格式控制,例如:
- %c:输出一个字符。c 是 character 的简写。
- %s:输出一个字符串。s 是 string 的简写。
- %hd:用来输出 short int 类型,hd 是 short decimal 的简写;
-
%d:
用来输出 int 类型,d 是 decimal 的简写; -
%ld:
用来输出 long int 类型,ld 是 long decimal 的简写。 - %f :以十进制形式输出 float 类型,f 是 float 的简写。
- %lf :以十进制形式输出 double 类型;
- %e :以指数形式输出 float 类型,输出结果中的 e 小写;
- %E :以指数形式输出 float 类型,输出结果中的 E 大写;
- %le :以指数形式输出 double 类型,输出结果中的 e 小写;
- %lE :以指数形式输出 double 类型,输出结果中的 E 大写。
4.整数
int 是基本的整数类型,short 和 long 是在 int 的基础上进行的扩展,short 可以节省内存,long 可以容纳更大的值。short、int、long 是C语言中常见的整数类型,其中 int 称为整型,short 称为短整型,long 称为长整型。
长度(所占字节数)关系为:2字节 ≤ short ≤ int ≤ long
在64位环境下,不同的操作系统会有不同的结果,如下所示(长度以字节计):
操作系统 | short | int | long |
---|---|---|---|
Win64(64位 Windows) | 2 | 4 | 4 |
类Unix系统(包括 Unix、Linux、Mac OS、BSD、Solaris 等) | 2 | 4 | 8 |
//获取某个数据类型的长度----sizeof 操作符
#include <stdio.h>
#include <stdlib.h>
int main()
{
short a = 10;
int b = 100;
int short_length = sizeof a;//如果后面跟的是变量名称,那么可以省略( )
int int_length = sizeof(b);
int long_length = sizeof(long);//如果跟的是数据类型,就必须带上( )
int char_length = sizeof(char);
printf("short=%d,int=%d,long=%d,char=%d\n", short_length, int_length, long_length, char_length);
}
//在32位环境以及Win64环境下的运行结果为:short=2,int=4,long=4,char=1
//不同整型的输出
#include <stdio.h>
#include <stdlib.h>
int main()
{
short a = 10;
int b = 100;
long c = 1000;
printf("a=%hd,b=%d,c=%ld\n",a,b,c);
}
//在编写代码的过程中,建议将格式控制符和数据类型严格对应起来,结果为:a=10,b=100,c=1000
总结:a、获取某个数据类型的长度可以使用 sizeof 操作符
b、数字有正负之分,在C语言中也是一样。最高位表示符号位,在符号位中,用0表示正数,用1表示负数。如果不希望设置符号位,可以在数据类型前面加 unsigned,如unsigned int n = 100;
c、当数值过大或过小时,有限的几个字节就不能表示,就会发生溢出。发生溢出时,最高位会被截去。
5.浮点数
C语言中常用的小数有两种类型,分别是 float 或 double;float 称为单精度浮点型,double 称为双精度浮点型。不像整数,小数没有那么多幺蛾子,小数的长度是固定的,float 始终占用4个字节,double 始终占用8个字节。
#include <stdio.h>
#include <stdlib.h>
int main()
{
float a = 0.302;
float b = 128.101;
double c = 123;
float d = 112.64E3;
double e = 0.6723e-2;
float f = 1.23002398;
printf("a=%e \nb=%f \nc=%lf \nd=%lE \ne=%lf \nf=%f \n",a,b,c,d,e,f);
return 0;
}
//运行结果:
a=3.020000e-001
b=128.100998
c=123.000000
d=1.126400E+005
e=0.006723
f=1.230024
对代码的说明:
1) %f 和 %lf 默认保留六位小数,不足六位以 0 补齐,超过六位按四舍五入截断。
2) 将整数赋值给 float 变量时会变成小数。
3) 以指数形式输出小数时,输出结果为科学计数法;也就是说,尾数部分的取值为:0 ≤ 尾数 < 10。
4) b 的输出结果让人费解,才三位小数,为什么不能精确输出,而是输出一个近似值呢?这和小数在内存中的存储形式有关,很多简单的小数压根不能精确存储,所以也就不能精确输出
在C语言中,整数和小数之间可以相互赋值:
- 将一个整数赋值给小数类型,在小数点后面加 0 就可以,加几个都无所谓。
- 将一个小数赋值给整数类型,就得把小数部分丢掉,只能取整数部分,这会改变数字本来的值。注意是直接丢掉小数部分,而不是按照四舍五入取近似值。将小数赋值给整数类型时会“失真”,所以编译器一般会给出警告。
6.字符与字符串
字符类型是 char,它的长度是 1,只能容纳 ASCII码表中的字符,也就是英文字符。字符类型由单引号' '
包围,字符串由双引号" "
包围。
//字符
#include <stdio.h>
#include <stdlib.h>
int main()
{
char a = '1';
char b = '$';
char c = 'A';
char d = ' ';// 空格也是一个字符
char e = '汉';//打印有问题,ASCII编码之外的字符
putchar(a);
putchar('\n');//putchar 函数每次只能输出一个字符,输出多个字符需要调用多次。
printf("%c,%c,%c,%c,%c\n",a,b,c,d,e);
char ee = 'E';
char ff = 70; //字符类型赋值整数
int gg = 71;
int hh = 'H';//整数类型赋值字符
printf("ee:%c,%d\n",ee,ee);
printf("ff:%c,%d\n",ff,ff);
printf("gg:%c,%d\n", gg, gg);
printf("hh:%c,%d\n", hh, hh);
return 0;
}
//输出结果:
1
1,$,A, ,?
ee:E,69
ff:F,70
gg:G,71
hh:H,72
在ASCII码表中,字符 'E'、'F'、'G'、'H' 对应的编号分别是 69、70、71、72。ee、ff、gg、hh 实际上存储的都是整数:
①当给 ee、hh 赋值一个字符时,字符会先转换成 ASCII 码再存储;
②当给 ff、gg 赋值一个整数时,不需要任何转换,直接存储就可以;
③当以 %c 输出 ee、ff、gg、hh时,会根据 ASCII 码表将整数转换成对应的字符;
④当以 %d 输出 ee、ff、gg、hh时,不需要任何转换,直接输出就可以。
在C语言中没有专门的字符串类型,我们只能使用数组或者指针来间接地存储字符串。
//字符串
#include <stdio.h>
#include <stdlib.h>
int main()
{
char web_url[] = "www.baidu.com";
char *web_name = "百度";
printf("%s\n%s\n",web_url,web_name);
return 0;
}
//输出结果:
www.baidu.com
百度
注: 字符串的两种定义形式①char str1[] = "www.baidu,com";②char *str2 = "百度官网";其实是有区别的,第一种形式的字符串所在的内存既有读取权限又有写入权限,第二种形式的字符串所在的内存只有读取权限,没有写入权限。printf()、puts() 等字符串输出函数只要求字符串有读取权限,而 scanf()、gets() 等字符串输入函数要求字符串有写入权限,所以,第一种形式的字符串既可以用于输出函数又可以用于输入函数,而第二种形式的字符串只能用于输出函数。
另外,对于第一种形式的字符串,在[]
里面要指明字符串的最大长度,如果不指明,也可以根据 =
后面的字符串来自动推算,此处,就是根据"www.baidu.com"
的长度来推算的。但是如果我们只是定义了一个字符串,并没有立即给它赋值,这样导致没法自动推算,只能手动指明最大长度,可以写作char xxx[30]
,而不能写作char xxx[]
。
7.转义字符
在C语言中,一个字符除了可以用它的实体(也就是真正的字符)表示,还可以用编码值表示。这种使用编码值来间接地表示字符的方式称为转义字符(Escape Character)。转义字符以 \
或者 \x
开头,以 \
开头表示后跟八进制形式的编码值,以 \x
开头表示后跟十六进制形式的编码值。对于转义字符来说,只能使用八进制或者十六进制。
//转义字符既可以用于单个字符,也可以用于字符串
char a = '\61'; //字符1
char b = '\141'; //字符a
char c = '\x31'; //字符1
char d = '\x61'; //字符a
char *str1 = "\x31\x32\x33\x61\x62\x63"; //字符串"123abc"
char *str2 = "\61\62\63\141\142\143"; //字符串"123abc"
//字符串中可以同时使用八进制形式和十六进制
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("\x68\164\164\x70://xjtuse.\x6e\145\x74\n");
return 0;
}
//运行结果:
http://xjtuse.net
\n
和\t
是最常用的两个转义字符:
①\n
用来换行,让文本从下一行的开头输出,前面的章节中已经多次使用;
②\t
用来占位,一般相当于四个空格,或者 tab 键的功能。
③单引号、双引号、反斜杠是特殊的字符,不能直接表示:
单引号是字符类型的开头和结尾,要使用\'
表示,也即'\''
;
双引号是字符串的开头和结尾,要使用\"
表示,也即"abc\"123"
;
反斜杠是转义字符的开头,要使用\\
表示,也即'\\'
,或者"abc\\123"
。
8.标识符
标识符就是程序员自己起的名字,除了变量名,后面还会讲到函数名、宏名、结构体名等,它们都是标识符。不过,名字也不能随便起,要遵守规范;C语言规定,标识符只能由字母(A~Z, a~z)、数字(0~9)和下划线(_)组成,并且第一个字符必须是字母或下划线,不能是数字。
在使用标识符时还必须注意以下几点:
- C语言虽然不限制标识符的长度,但是它受到不同编译器的限制,同时也受到操作系统的限制。例如在某个编译器中规定标识符前128位有效,当两个标识符前128位相同时,则被认为是同一个标识符。
- 在标识符中,大小写是有区别的,例如 BOOK 和 book 是两个不同的标识符。
- 标识符虽然可由程序员随意定义,但标识符是用于标识某个量的符号,因此,命名应尽量有相应的意义,以便于阅读和理解,作到“见名知意”。
9.关键字
关键字(Keywords)是由C语言规定的具有特定意义的字符串,通常也称为保留字。可以将关键字理解为具有特殊含义的标识符,它们已经被系统使用,我们不能再使用了。
10.注释
注释(Comments)可以出现在代码中的任何位置,用来向用户提示或解释代码的含义。程序编译时,会忽略注释,不做任何处理,就好像它不存在一样。
C语言支持单行注释和多行注释:
- 单行注释以
//
开头,直到本行末尾(不能换行); - 多行注释以
/*
开头,以*/
结尾,注释内容可以有一行或多行。
11.表达式和语句
表达式(Expression)和语句(Statement)的概念在C语言中并没有明确的定义:
- 表达式可以看做一个计算的公式,往往由数据、变量、运算符等组成,例如
3*4+5
、a=c=d
等,表达式的结果必定是一个值; - 语句的范围更加广泛,不一定是计算,不一定有值,可以是某个操作、某个函数、选择结构、循环等。
总结:
- 表达式必须有一个执行结果,这个结果必须是一个值,例如
3*4+5
的结果 17,a=c=d=10
的结果是 10,printf("hello")
的结果是 5(printf 的返回值是成功打印的字符的个数)。 - 以分号
;
结束的往往称为语句,而不是表达式,例如3*4+5;
、a=c=d;
等。
12.加减乘除运算
#include <stdio.h>
#include <stdlib.h>
int main()
{
//加减乘除
int a = 12;
int b = 100;
float c = 8.5;
int m = a + b;//加法
float n = b * c; //乘法
double p = a / c;//除法
int q = b % a; //取余,取余运算只能针对整数
printf("m=%d,n=%f,p=%lf,q=%d \n",m,n,p,q); //m=112,n=850.000000,p=1.411765,q=4
//除法
int d = 100;
int e = 12;
float f = 12.0;
double j = d / e; //d,e都是整数,结果也是整数,不能整除,直接丢掉小数部分,只保留整数部分。
double k = d / f; //一旦除数和被除数中有一个是小数,那么运算结果也是小数,并且是 double 类型的小数。
printf("j=%lf,k=%lf \n",j,k); //j=8.000000,k=8.333333
//加减乘除简写
int a1 = 10;
int a2 = 20;
a1 += 10; //a1=20
a1 *= (a2 - 10);//a1=200
a1 -= (a2 + 20);//a1=160
printf("a1=%d \n",a1);//160
return 0;
}
//注:a = a # b,可以简写为:a #= b,其中# 表示 +、-、*、/、% 中的任何一种运算符。
当一个表达式中出现多个运算符时,C语言会先比较各个运算符的优先级,按照优先级从高到低的顺序依次执行;当遇到优先级相同的运算符时,再根据结合性决定先执行哪个运算符:如果是左结合性就先执行左边的运算符,如果是右结合性就先执行右边的运算符。
13.自增(++)和自减(--)
++
和 --
分别称为自增运算符和自减运算符,需要重点说明的是,++ 在变量前面和后面是有区别的:
- ++ 在前面叫做前自增(例如 ++a)。前自增先进行自增运算,再进行其他操作。
- ++ 在后面叫做后自增(例如 a++)。后自增先进行其他操作,再进行自增运算。
自减(--)也一样,有前自减和后自减之分。
#include <stdio.h>
int main()
{
int a = 10, b = 20, c = 30, d = 40;
int a1 = ++a, b1 = b++, c1 = --c, d1 = d--;
printf("a=%d,a1=%d\n",a,a1);//a=11,a1=11
printf("b=%d,b1=%d\n",b,b1);//b=21,b1=20
printf("c=%d,c1=%d\n",c,c1);//c=29,c1=29
printf("d=%d,d1=%d\n",d,d1);//d=39,d1=40
return 0;
}
//解析:a,b,c,d我们容易理解,接下来重点分析a1、b1、c1、d1:
1.对于a1=++a,先执行 ++a,结果为 11,再将 11 赋值给 a1,所以 a1 的最终值为11。而 a 经过自增,最终的值也为 11。
2.对于b1=b++,b 的值并不会立马加 1,而是先把 b 原来的值交给 b1,然后再加 1。b 原来的值为 20,所以 b1 的值也就为 20。而 b 经过自增,最终值为 21。
3.对于c1=--c,先执行 --c,结果为 29,再将 29 赋值给c1,所以 c1 的最终值为 29。而 c 经过自减,最终的值也为 29。
4.对于d1=d--,d 的值并不会立马减 1,而是先把 d 原来的值交给 d1,然后再减 1。d 原来的值为 40,所以 d1 的值也就为 40。而 d 经过自减,最终值为 39。
可以看出:a1=++a;会先进行自增操作,再进行赋值操作;而b1=b++;会先进行赋值操作,再进行自增操作。c1=--c;和d1=d--;也是如此。
#include <stdio.h>
int main()
{
int a = 12, b = 1;
int c = a - (b--);//①
int d = (++a) - (--b);//②
printf("c=%d,d=%d\n",c,d);//c=11,d=14
return 0;
}
//解析:1.执行语句①时,因为是后自减,会先进行a-b运算,结果是 11,然后 b 再自减,就变成了 0;最后再将a-b的结果(也就是11)交给 c,所以 c 的值是 11。
//2.执行语句②之前,b 的值已经变成 0。对于d=(++a)-(--b),a 会先自增,变成 13,然后 b 再自减,变成 -1,最后再计算13-(-1),结果是 14,交给 d,所以 d 最终是 14。
14.数据类型转换
(1)自动类型转换
自动类型转换就是编译器默默地、隐式地、偷偷地进行的数据类型转换,这种转换不需要程序员干预,会自动发生。将一种类型的数据赋值给另外一种类型的变量时就会发生自动类型转换,如float f = 100,100 是 int 类型的数据,需要先转换为 float 类型才能赋值给变量 f。在不同类型的混合运算中,编译器也会自动地转换数据类型,将参与运算的所有数据先转换为同一种类型,然后再进行计算。转换的规则如下:
①转换按数据长度增加的方向进行,以保证数值不失真,或者精度不降低。例如,int 和 long 参与运算时,先把 int 类型的数据转成 long 类型后再进行运算。
②所有的浮点运算都是以双精度进行的,即使运算中只有 float 类型,也要先转换为 double 类型,才能进行运算。
③char 和 short 参与运算时,必须先转换成 int 类型。
下图对这种转换规则进行了更加形象地描述:
(2)强制类型转换
自动类型转换是编译器根据代码的上下文环境自行判断的结果,有时候并不能满足所有的需求。如果需要,程序员也可以自己在代码中明确地提出要进行类型转换,这称为强制类型转换。
自动类型转换是编译器默默地、隐式地进行的一种类型转换,不需要在代码中体现出来;强制类型转换是程序员明确提出的、需要通过特定格式的代码来指明的一种类型转换。换句话说,自动类型转换不需要程序员干预,强制类型转换必须有程序员干预。
#include <stdio.h>
int main()
{
int sum = 100;
int count = 7;
double average;
average = (double)sum / count;
printf("Average is %lf \n",average);//Average is 14.285714
return 0;
}
//解析:1.sum 和 count 都是 int 类型,如果不进行干预,那么sum / count的运算结果也是 int 类型,小数部分将被丢弃;虽然是 average 是 double 类型,可以接收小数部分,但是心有余力不足,小数部分提前就被“阉割”了,它只能接收到整数部分,这就导致除法运算的结果严重失真。
//2.上述代码将 sum 强制转换为 double 类型,这样sum / count的结果也将变成 double 类型,就可以保留小数部分了,average 接收到的值也会更加精确。
//3.对于除法运算,如果除数和被除数都是整数,那么运算结果也是整数,小数部分将被直接丢弃;如果除数和被除数其中有一个是小数,那么运算结果也是小数。
//4.( )的优先级高于/,对于表达式(double) sum / count,会先执行(double) sum,将 sum 转换为 double 类型,然后再进行除法运算,这样运算结果也是 double 类型,能够保留小数部分。注意不要写作(double) (sum / count),这样写运算结果将是14.000000,仍然不能保留小数部分。
总结:a、无论是自动类型转换还是强制类型转换,都只是为了本次运算而进行的临时性转换,转换的结果也会保存到临时的内存空间,不会改变数据本来的类型或者值。
b、可以自动转换的类型一定能够强制转换,但是,需要强制转换的类型不一定能够自动转换 。
c、可以自动进行的类型转换一般风险较低,不会对程序带来严重的后果,例如,int 到 double 没有什么缺点,float 到 int 顶多是数值失真。强制进行的类型转换一般风险较高,或者行为匪夷所思,例如,char * 到 int * 就是很奇怪的一种转换,这会导致取得的值也很奇怪,再如,int 到 char * 就是风险极高的一种转换,一般会导致程序崩溃。
参考文献:C语言中文网
上一篇: 变量和数据类型
下一篇: ORA-01461错误解决