变量与常量
为了能够更加方便的使用数据,程序员会将在程序运行期间会改变或赋值的数据使用变量进行保存。常量则是预先定义好,在程序运行期间不会改变的固定值
变量和常量就好像是一个盒子,可以用来装东西(数据)。在计算机中,数据是存放在内存中的,存放数据的内存空间程序员为了方便以后的使用,都会起一个好记的名字。这个名称也由字母、数字和下划线组成,必须要以字母和下划线开头。由于C语言是对大小写敏感的,所以大写字母和小写字母是不同的,也就是变量名abc和Abc是两个不同的变量。
数据类型
不同的数据类型有不同的含义,有的数据类型表示整数,有的表示字符,有的表示浮点数。常量可以是任何的数据类型,通过常量的值识别(100是整数,123.45是浮点数)。而变量则需要指定数据类型。在C语言中有很多种数据类型,从最初的K&R给出的7个数据类型关键字,再到C90添加的2个新的关键字,到最后的C99添加的3个关键字
最初K&R给出的关键字 | C90标准添加的关键字 | C99标准添加的关键字 |
---|---|---|
int | signed | _Bool |
long | void | _Complex |
short | _Imaginary | |
unsigned | ||
char | ||
float | ||
double |
int、long、short、unsigned和C90新添加的signed关键字用于表示整数类型,unsigned表示无符号数,signed则表示有符号数,整数类型的例子:unsigned short int 和 long long int
char关键字用于存放字母和其他字符(如:$、%等),char也可以用来表示较小的整数
float、double和long double表示浮点数
_Bool表示布尔类型(true或false)
_Complex和_Imaginary分别表示复数和虚数
存储大小
不同的数据类型可以存放的数据大小是不同的,能够存放的数据越多,值越大。
数据类型 | 存储大小 |
---|---|
char | 1字节 |
int | 2或4字节() |
short | 2字节 |
long | 4字节 |
float | 4字节 |
double | 8字节 |
long double | 16字节 |
在C语言中,可以通过sizeof
运算符查看数据类型存储字节的大小
#include
int main()
{
printf("char 的存储大小: %d\n", sizeof(char));
printf("short 的存储大小: %d\n", sizeof(short));
printf("int 的存储大小: %d\n", sizeof(int));
printf("long 的存储大小: %d\n", sizeof(long));
printf("float 的存储大小: %d\n", sizeof(float));
printf("double 的存储大小: %d\n", sizeof(double));
printf("long double 的存储大小: %d\n", sizeof(long double));
return 0;
}
运行结果:
这里使用到的printf()函数,后面会进行讲解,目前只需要知道printf()是用来打印内容到屏幕上的就可以了。
变量的定义
C语言提供了很多种数据类型,在定义变量时需要指定变量的数据类型,如整数可以使用int类型,小数可以使用float类型等,下面将开始介绍如何定义变量。
定义变量
定义变量的语法:数据类型 变量名;
举个例子,下面的程序用于计算两个整数的和
#include
int main()
{
int num1; //定义一个变量num1,用于存放第一个数
int num2; //定义一个变量num2,用于存放第二个数
int sum; //定义一个变量sum,用于存放两个数的和
num1 = 100; //将整数100赋值给变量num1
num2 = 200;
sum = num1 + num2; //计算num1与num2相加,并将值赋值给sum变量
printf("num1 + num2 = %d\n", sum); // 将sum的值打印到屏幕
return 0;
}
运行结果:
在声明num1和num2变量时,并没有给它们提供初始值。num1和num2是通过后面的 num1 = 100;
和 num2 = 200;
获取到值的,这种行为称为初始化。初始化就是给变量一个初始值。
定义变量时初始化
在上面的例子中,定义变量与给初始化变量是分成两步的,也可以将这两步合并在一起,将程序修改后如下:
#include
int main()
{
int num1 = 100; //定义变量num1并将100赋值给num1
int num2 = 200;
int sum = num1 + num2; //num1与num2相加后的结果赋值给sum变量
printf("num1 + num2 = %d\n", sum); // 将sum的值打印到屏幕
return 0;
}
连续定义多个变量
修改后的程序比之前的简洁了,接着我们发现num1和num2的数据类型是相同的,都是int类型,那么这两个变量就可以一起定义,还是上面的例子,修改后如下:
#include
int main()
{
int num1 = 100, num2 = 200; //同时声明num1和num2并赋值
int sum = num1 + num2; //num1与num2相加后的结果赋值给sum变量
printf("num1 + num2 = %d\n", sum); // 将sum的值打印到屏幕
return 0;
}
需要注意的是,为了不让人误会在同时定义多个变量时,有的变量不需要初始化,而有的变量需要进行初始化。那么不建议写在一起,建议分开定义,如int num1, num2= 200;
这样写很容易让人误以为num1和num2的值都是200,所以不建议这么写,当然这么写是不会报错的。
常量的定义
通过一个例子开始讲解常量的使用,下面的程序的作用是计算圆的周长和面积:
#include
int main(void)
{
float pi = 3.14159; //圆周率
int r = 5; //圆的半径
float area = pi * r * r; //计算圆的面积
float circum = 2 * pi * r; //计算圆的周长
printf("半径为 %d 的圆面积是: %.2f, 周长是: %.2f", r, area, circum);
return 0;
}
通过上面的例子可以知道变量也可以当做常量来使用,但是在实际使用的时候不建议这么使用,因为在程序运行过程中有可能会将定义的变量改变,变量并非不可改变的。那有没有更好的定义常量的方法呢?有,这就需要使用预处理语句定义一个常量,这样定义的常量也被称为符号常量。语法是:#define NAME value
使用预处理语句,修改上面的例子:
#include
#define PI 3.14159 //使用预处理语句定义圆周率
int main(void)
{
int r = 5; //圆的半径
float area = PI * r * r; //计算圆的面积
float circum = 2 * PI * r; //计算圆的周长
printf("半径为 %d 的圆面积是: %.2f, 周长是: %.2f", r, area, circum);
return 0;
}
预处理语句的作用是在编译的时候将PI替换为3.14159,这样就达到了常量的作用了。预处理语句一定要写在顶部,并且PI和3.14159之间是没有等号和结束时也没有分号的。前面也说了预处理语句是在编译时替换掉值,如果有了等号和分号,那么等号和分号也会变成要替换的值了。
还有一种方法是使用限定符 const
将变量限定为只读,修改上面的例子如下:
#include
int main(void)
{
const float pi = 3.14159; //使用限定符const,定义常量圆周率
int r = 5; //圆的半径
float area = pi * r * r; //计算圆的面积
float circum = 2 * pi * r; //计算圆的周长
printf("半径为 %d 的圆面积是: %.2f, 周长是: %.2f", r, area, circum);
return 0;
}
使用 const
限定符定义的变量pi可以使用,可以使用 printf()
打印值,但就是无法修改值。另外需要注意的是使用 const
限定符定义的是变量,不是常量。
输入输出
输出
在C语言中,通过printf()
将内容输出到屏幕上,printf()
也被称为格式化输出,可以让变量以某种格式输出到屏幕上,如 %d为以整数的形式显示,%f以浮点数的形式显示,下表列出了一些转换说明和各自对应的输出类型:
转换说明 | 输出 |
---|---|
%a | 浮点数、十六进制和p记数法 |
%A | 浮点数、十六进制和p记数法 |
%c | 单个字符 |
%d | 有符号十进制整数 |
%e | 浮点数,e记数法 |
%E | 浮点数,e记数法 |
%f | 浮点数,十进制记数法 |
%g | 根据值的不同,自动选择%f或%e。%e格式用于指数小于-4或者大于或等于精度时 |
%G | 根据值的不同,自动选择%f或%E。%E格式用于指数小于-4或者大于或等于精度时 |
%i | 有符号十进制整数(与%d相同) |
%o | 无符号八进制整数 |
%p | 指针 |
%s | 字符串 |
%u | 无符号十进制整数 |
%x | 无符号十六进制整数,使用十六进制数0f |
%X | 无符号十六进制整数,使用十六进制数0F |
%% | 打印一个百分号 |
一开始不需要全部都记住,只需要记住几个常用的(如:%d,%f,%c)即可,其他的用到在看就可以了,用的多了就记住了。下面的例子是将一个整数以八进制和十六进制的形式显示:
#include
int main(void)
{
int num = 100;
printf("100 的八进制为 %o\n", num); //显示num的八进制
printf("100 的八进制为 %#o\n", num); //显示num的八进制时,并在前面加上前缀
printf("100 的十六进制为 %x\n", num); //显示num的十六进制
printf("100 的十六进制为 %#x\n", num); //显示num的十六进制,并在前面加上前缀
return 0;
}
运行结果:
可以通过printf()函数配合%o和%x打印出数值的八进制形式和十六进制形式。#只是为了显示前缀,八进制以0开头,十六进制以0x开头
有的读者可能不太了解什么是八进制和十六进制,那就简单的介绍一下。我们日常说的10,100,123等数字,都是十进制,可以发现这些数字都是逢十进一,而八进制和十六进制同理,八进制就是逢八进一,十六进制是逢十六进一。有的读者就会问了,逢十六进一?那要怎么表示十以上的数呀?十六进制由1到9和a到f(也可以是A到F)组成,没错,a到f分别表示10到15。
参数与陷阱
在使用printf()
和 scanf()
时需要确保转换说明的数量、类型与后面的参数数量、类型匹配。由于 printf()
和 scanf()
的参数是可变的,所以无法检查出参数的个数和类型是否正确。
那如果参数个数不匹配会怎样呢?
#include
int main(void)
{
int num1 = 1;
float num2 = 1.23;
printf("%d %d %d\n", num1, num1, num1, num1); //参数太多
printf("%d %d %d\n", num1); //参数太少
printf("%d\n", num2); //类型不匹配
return 0;
}
运行结果:
当使用%d打印一个浮点数时,不会将这个浮点数转换为int类型。在不同的平台下,缺少参数或参数类型不匹配导致的结果会不相同。
输入
在前面的两个数相加的例子中,两个相加的数是预先就设置好的,当要计算其他整数相加时,需要修改源文件并生成新的可执行文件,这就很麻烦,这是就可以让用户输入要进行计算的两个整数。
要想获取用户的输入可以使用scanf()
进行接收,使用的方法与printf()
相似,%d表示要接收整数,%f表示要接收一个浮点数。
修改程序:
#include
int main(void)
{
int num1, num2; //同时声明num1和num2
scanf("%d %d", &num1, &num2); //获取用户输入的num1和num2的值
int sum = num1 + num2; //num1与num2相加后的结果赋值给sum变量
printf("num1 + num2 = %d\n", sum); // 将sum的值打印到屏幕
return 0;
}
运行结果:
运行完程序,应该可以发现如果这是给程序员自己使用,那当然没有任何问题,但是给其他人使用,会出现不知道要干嘛的情况。所以应该在程序接收用户输入之前,告诉用户要做什么,再次修改程序:
#include
int main(void)
{
int num1, num2; //同时声明num1和num2
printf("请输入两个需要相加的整数(例:12 34):"); //提示用户输入两个整数
scanf("%d %d", &num1, &num2); //获取用户输入的num1和num2的值
int sum = num1 + num2; //num1与num2相加后的结果赋值给sum变量
printf("num1 + num2 = %d\n", sum); // 将sum的值打印到屏幕
return 0;
}
运行结果:
细心的读者会发现在num1和num2的前面有一个&符号,那这个&符号表示的是什么意思呢?&符号表示获取地址,整个语句就是将获取到的整数放入到num1和num2的地址中。
当读取的是基本的数据类型的值时需要使用&符号,而读取的是字符串时,不要使用&符号。
整数溢出
当一个整数类型超出了它能表示的最大值会出现溢出,通过一个例子观察溢出后的结果
#include
int main(void)
{
int i = 2147483647; //定义一个有符号整数类型的变量i并初始化为最大值
unsigned int j = 4294967295; //定义一个无符号整数类型的变量j并初始化为最大值
printf("%d %d %d\n", i, i+1, i+2);
printf("%u %u %u\n", j, j+1, j+2);
return 0;
}
运行结果:
通过观察可以发现当一个数超过自身能够表示的最大值时,会从最小值开始。当发生溢出时系统没有告诉用户,所以在编程时需要注意。
char类型
char类型用于存放字符(如:字母或标点符号),但char其实是整数类型,因为char实际上保存的是整数,而不是字符。有的读者就会有疑问了,存储整数,那要怎么知道是哪个字符呢?在计算机中使用数字编码处理字符,即使用整数表示字符,在ASCII编码中65表示大写字母"A",标准的ASCII的范围是0~127,完全可以使用char类型进行存储
声明char类型变量
char类型的变量在赋值时,值需要使用单引号括起来,如:char ch = 'A';
,而不能是 char ch = A;
。如果没有被单引号括起来,此时A表示一个变量名,而不是字符A。
char类型也可以直接保存整数,如 char ch = 65;
,但是不建议这样使用,这样使用需要系统支持ASCII码,最好还是使用 ‘A’ 替换 65。
举个例子
用户输入一个字符,将这个字符对应的ASCII码打印在屏幕上:
#include
int main(void)
{
char ch; //定义变量ch,用于接收用户输入
printf("请输入一个字符(如:A):");
scanf("%c", &ch); //接收用户输入的字符
printf("%c 对应的整数为 %d\n", ch, ch);
return 0;
}
运行结果:
转义字符
使用特殊的符号序列表示一些特殊的字符,这些符号序列就叫做转义序列,下表列出了转义序列及其含义
转义序列 | 含义 |
---|---|
\a | 警报(ANSI C) |
\b | 退格 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 水平制表符(相当于tab键) |
\v | 垂直制表符 |
\\ | 反斜杠() |
\' | 单引号 |
\" | 双引号 |
? | 问号 |
\0oo | 八进制值(oo必须是有效八进制数) |
\xhh | 十六进制值(hh必须是有效十六进制数) |
通过一个例子更好的理解转义字符:
#include
int main(void)
{
float salary;
printf("\aEnter your desired monthly salary:");
printf(" $_______\b\b\b\b\b\b\b");
scanf("%f", &salary);
printf("\n\t$%.2f a month is $%.2f a year.", salary, salary * 12.0);
printf("\rGee!\n");
return 0;
}
运行结果:
解析:
在程序的第7行 printf("\aEnter your desired monthly salary:");
中的 \a
会发出警报的声音(能否发出警报取决于硬件,有可能不会发出警报)
接着第8行的 printf(" $_______\b\b\b\b\b\b\b");
先将$_______打印出来呢,接着使用 \b
移动光标,使光标紧跟$符
在第10行的 printf("\n\t$%.2f a month is $%.2f a year.", salary, salary * 12.0);
首先是 \n
进行换行,接着是制表符 \t
使后面的内容缩进。光标停留在最后的点那里(.),如下图:
在第11行的 printf("\rGee!\n");
\r
将光标移动到当前行是起始位置,再打印内容,最后的 \n
换行
reference
《C Primer Plus》(第六版)