2. 变量和数据类型
变量
什么是变量?顾名思义,变量是一个可以被改变的量。在计算机中,变量是用来存储数据的容器。变量可以存储各种类型的数据,如整数、浮点数、字符串等。
在 C 语言中,变量可以被声明,你需要在声明时指定变量的类型和名称。
此外,你可以使用 赋值符号(=) 来给变量赋值。
int age = 10;
float number = 3.14159;
char letter = 'A';
提示
赋值符号(=)是 C 语言中的赋值运算符,用于将右侧的值赋给左侧的变量。
赋值符号的左侧只能是指定的变量,右侧可以是任何结果满足被赋值变量类型的表达式。
如果许多个变量具有相同的类型,你可以使用逗号分隔符来声明多个变量:
int a, b, c;
变量的作用域
变量的作用域表示该变量在程序中可以被正确访问的范围。
对于于全局变量,它们在整个程序中都可以被访问。全局变量通常在函数外部声明:
// 这里不可以访问 PI
int PI = 3.14159; // 全局变量
// 这里可以访问 PI
int main() {
// 这里可以访问 PI
}
提示
为保证全局变量的安全性,一般将全局变量声明为常量或静态变量。
常量和静态变量的含义会在稍后提到
而对于大括号 {} 内部声明的变量,它们只能在当前大括号内被访问。这种变量被称为局部变量:
局部变量一旦离开其声明的大括号,就会立刻被销毁,此后再也无法访问。
此外,变量需要先声明才能够被使用
int main() {
// 这里也访问不到 a
int a = 10; // 局部变量
// 这里可以访问到 a
}
// 这里访问不到 a
常量和静态变量
常量(const)和静态变量(static)是 C 语言中的两种特殊变量。
常量是一种不能被修改的变量。在声明常量时,需要使用 const 关键字。常量通常用于表示不会改变的值,如圆周率、自然对数的底数等。
const float PI = 3.14159;
静态变量是一种在整个程序运行期间都存在的变量。在声明静态变量时,需要使用 static 关键字。静态变量通常用于表示需要在函数调用之间保持状态的变量,如计数器、累加器等。
static int counter = 0;
静态变量的特点在于,即使离开变量所在的作用域,静态变量不会被销毁,并仍然保持其值不变:
int counter() {
static int count = 0;
count = count + 1;
return count;
}
例如,每次调用上面的 counter() 函数,count 的值都会增加 1,而不是每次都返回 1。
变量的命名规则
在 C 语言中,变量命名我们一般遵循以下规则
- 对于一般的局部变量和函数,变量名一般使用小写字母,单词之间使用下划线
_分隔,即snake_case命名法 - 也可以使用匈牙利命名法,即变量名以类型前缀开头,之后名称每个单词首字母大写,如
int iAge,float fpNumber,但个人觉得这种命名方式非常丑而且没有必要。 - 对于常量,变量名一般使用大写字母,单词之间使用下划线
_分隔,即UPPER_CASE命名法 - 其它的命名规则会在说到对应的语法时提到
数据类型
在 C 语言中,数据类型用于表示变量可以存储的数据类型。C 语言中的数据类型包括以下几种:
- 整型(
short、int、long long)(对应类型分signed和unsigned) - 浮点型(
float、double、long double) - 字符型(
char) - 布尔型(
bool)(C 语言需要包含头文件stdbool.h) - 其它类型(进度未解锁)
整型
short
short 是一种较小的整数类型,占用 2 个字节(16 位)的内存空间。
既然它占用了二进制上 16 位的空间,那么它最大能是多少呢?
首先,当我们定义整型类型时,它默认是 signed 整型类型,即代表它有符号,因此,还需要牺牲一位二进制位表示符号的正负
因此,short 实际上只拥有 15 位来表示数值,而 15 位二进制数能够表示的最大值是 32767,最小值是 -32768
而如果你不需要符号,你可以在定义 short 类型时,使用 unsigned 关键字来表示无符号整型,这样,short 就拥有 16 位来表示数值,因此,它的最大值是 65535,最小值是 0:
unsigned short a = 65535;
之后的整型类型只在存储空间上有区别,之后的就不详细讲解了
提示
为啥最小值可以比最大值多 1?
因为 short 的二进制表示中,最高位是符号位,当符号位为 1 时,表示负数,当符号位为 0 时,表示正数。
因此,当符号位为 1 时,表示负数,而负数的表示方式是补码表示法,即最高位为 1(表示为负数),其余位取反,然后加 1。
那 -0 这个数怎么办呢?在这个时候,符号位就会被直接解释为负数,因此,-0 就被解释为 -32768。
这也是为什么,当你取 32767 + 1 时,得到的却是 -32768 了
为什么负数要使用补码表示法?
减法需要退位,但计算机的设计是每位分开计算的,只能用 1 位的临时寄存器来记录进位,退位操作根本无法实现
因此,出现了补码表示法,它是来想办法将减法转换为加法的
补码怎么想出来的?
如果说一个数减去 x 和一个数加上 y 相等,那么是不是可以得到,y = -x?
那不对啊,在二进制上,没有办法表示 -x 啊?
有的,兄弟,有的,回想起我们小学学过的整数除法,是不是有 商余 的概念?
举个例子让你回想起来:
4 ÷ 3 = 1 …… 1
7 ÷ 3 = 2 …… 1
那这样的话,4 和 7 在同时整除三之后,是不是相等的?
同理,对于 short 类型,能用来表示数字的只有 16 位,当我们得到的结果超过 16 位时,多出来的那一位就会被丢弃,因此,两个数字相加,实际上得到的结果是除以 的余数
那么我们回到除 3 的情景,如果我现在要得到 4 - 2 在整除 3 之后得到的结果,那显然它是 2
那有没有另外一个数字,能让 4 加上它再整除 3 之后得到的结果还是 2 呢?
答案自然是有的,我们找到最小的一个正整数,也就是 1
那么在同时除以三的基础上,加上 1 就相当于将原来的数字减去 2,因此,1 在这里就可以看作是 -2 的补码
再仔细一瞧,您猜怎么着?1 + 2 正好就是 3,也就是原本要被整除的那个数
那同理推断,一个负整数 short 的变量,加上它的补码,得到的结果就是原本要被整除的那个数
所以补码的值就很好计算了,只需要将原本的数字取反,然后加 1 就可以得到补码了
int
int 是一种标准的整数类型,占用 4 个字节(32 位)的内存空间。
通常情况下,int 的最大值是 2147483647(),最小值是 -2147483648。
提示
int 的大小范围建议牢记,一些题目可能会在数据范围上作文章,大多围绕 int 的范围来出题,因此要特别小心超出 int 范围的情况
long long
long long 是一种非常大的整数类型,占用 8 个字节(64 位)的内存空间。
浮点型
float
float 是一种单精度浮点数类型,占用 4 个字节(32 位)的内存空间。
double
double 是一种双精度浮点数类型,占用 8 个字节(64 位)的内存空间。
long double
long double 是一种扩展精度浮点数类型,占用 10/16 个字节(80 / 128 位)的内存空间。具体占用大小因平台而异
如果直接写小数运算,那么 C 语言默认使用的是 double 类型,一般来说,double 的大小也够用,一般不会用到 long double 类型
浮点型变量的存储格式采取 IEEE 754 格式,因为展开讲起来有些麻烦,所以就不展开了,一般来说考前看一眼就够用,一般也用不上这么精细
字符型
char
char 是一种字符类型,占用 1 个字节(8 位)的内存空间。
char 类型的变量可以存储一个字符,如字母、数字、符号等。
字符需要用单引号 ' 括起来,如 'A'、'1'、'!' 等。
char letter = 'A';
char 类型的变量实际上存储的是字符的 ASCII 码值,因此,char 类型的变量也可以存储整数,并参与整数运算:
int distance = 'a' - 'A';
在 C 语言中,char 默认是 signed 格式,这会导致它浪费了 128 个值,而 128 ~ 255 实际上是存在对应字符的,因此,在使用 char 输出 ASCII 码大于 127 的字符时,务必注意 char 此时是 unsigned 还是 signed 格式
布尔型
bool
bool 是一种二元类型,理论上应该只需要占用 1 个二进制位,但很遗憾,因为地址空间的申请必须按照字节大小对齐,因此,bool 类型实际上占用 1 个字节(8 位)的内存空间。
bool 类型的变量只能存储 true 或 false 两个值。
因此,实际上 true = 1,false = 0
bool flag = true;
输入输出
在 C 语言中,输入输出是使用 scanf 和 printf 函数来实现的,当然,也有 putchar,getchar 这种更加原始的方法。
要使用它们,需要先包含头文件 stdio.h
scanf
scanf 函数用于从标准输入读取数据,并将其存储到指定的变量中。
scanf 函数的语法如下:
int scanf(const char *format, ...);
提示
format 的类型目前尚未解锁,稍安勿躁哦~
其中,format 是一个字符串,用于指定输入数据的格式,... 表示可以接受任意数量的参数,这些参数是用于存储输入数据的变量。
例如,以下代码从标准输入读取一个整数和一个浮点数,并将其存储到 a 和 b 变量中:
int a;
float b;
scanf("%d%f", &a, &b);
其中,%d 表示读取一个整数,%f 表示读取一个浮点数,&a 和 &b 表示将读取的数据存储到 a 和 b 变量中。
这里的 & 表示取地址,含义是,将变量所存储的位置告诉 scanf 函数,这样 scanf 函数才能将读取的数据存储到这个位置。
提示
虽然 scanf 可以指定输入的格式,但实际上当你不指定格式,只是一味的写 %?时,scanf 还是会自动忽略两个输入之间的空白符,因此,反而不必担心输入的格式问题
提示
% 号表示法
% 号表示法用于指定输入输出的格式。例如,%d 表示输入输出一个整数,%f 表示输入输出一个浮点数,%c 表示输入输出一个字符,%s 表示输入输出一个字符串。
其它的一些用法会在之后提到,或可自行查阅
printf
printf 函数用于将数据输出到标准输出。
printf 函数的语法如下:
printf(const char *format, ...);
其中,format 是一个字符串,用于指定输出数据的格式,... 表示可以接受任意数量的参数,这些参数是要输出的数据。
例如,以下代码将整数 a 和浮点数 b 输出到标准输出:
int a = 10;
float b = 3.14;
printf("%d %f\n", a, b);
\n 表示换行符,它们俩合在一起表示一个字符
提示
\ 转义符号
\ 是转义符号,用于表示特殊的字符。例如,\n 表示换行符,\t 表示制表符,\" 表示双引号,\'表示单引号。
在字符串中,如果需要输出反斜杠,需要使用两个反斜杠 \\ 来表示一个反斜杠
提示
printf 的输出格式还能有更多花样,这里挑几个比较好用的:
- "%?d":输出一个整数,其中
?表示输出的位数,如果输出的位数不足,则会在前面补空格,如果超出位数,则照常输出不会有变化 - "%?f":和上一个同理,但是是浮点数
- "%x":输出一个整数,以十六进制的形式输出,也可以用来输入
- "%X":和上一个同理,但是是十六进制的大写形式
- "%.?d":输出一个整数,其中
?表示输出的位数,如果输出的位数不足,则会在前面补零,如果超出位数,则不会起作用 - "%.?f":输出一个浮点数,其中
?表示保留小数的位数,如果输出的位数不足,则会在后面补零,如果超出位数,则会将超出的部分截掉 - "%?.?d":相当于之前提到的两种方法的结合
- "%?.?f":同理
getchar
getchar 函数用于从标准输入严格读取下一个字符,这意味着读取的字符也包含换行符、空格等。
getchar 函数的语法如下:
int getchar();
getchar 函数返回读取的字符的 ASCII 码值
例如,以下代码从标准输入读取一个字符,并将其存储到 c 变量中:
char c;
c = getchar();
putchar
putchar 函数用于将一个字符输出到标准输出。
putchar 函数的语法如下:
int putchar(int c);
putchar 函数接受一个整数参数 c,表示要输出的字符的 ASCII 码值。
例如,以下代码将字符 c 输出到标准输出:
char c = 'A';
putchar(c);
运算符
算术运算符
算术运算符用于执行基本的算术运算,包括加法、减法、乘法、除法和取模运算。
+:加法运算符,用于将两个操作数相加。-:减法运算符,用于将一个操作数减去另一个操作数。*:乘法运算符,用于将两个操作数相乘。/:除法运算符,用于将一个操作数除以另一个操作数。对于整数除法,结果会舍去小数部分。%:取模运算符,用于计算两个操作数的余数。++:自增运算符,用于将操作数加 1。--:自减运算符,用于将操作数减 1。
提示
++ 和 -- 这两个运算符有说法,它们可以放在操作数的前面,也可以放在操作数的后面,放在前面表示先加 1 或减 1,再进行运算,放在后面表示先进行运算,再加 1 或减 1
例如:
int a = 1;
int b = ++a; // a = 2, b = 2
int c = a++; // c = 2, a = 3
位运算符
位运算符用于对整数进行位操作。
&:按位与运算符,用于将两个操作数的对应位进行与运算。|:按位或运算符,用于将两个操作数的对应位进行或运算。^:按位异或运算符,用于将两个操作数的对应位进行异或运算。~:按位取反运算符,用于将操作数的所有位进行取反运算。<<:左移运算符,用于将操作数的所有位向左移动指定的位数。>>:右移运算符,用于将操作数的所有位向右移动指定的位数。
注意
位运算符的优先级较低,因此在使用时需要注意运算符的优先级,建议多使用括号来明确运算顺序。
提示
位运算的速度比算术运算快很多,因此,在一些可以用位运算符替代算术运算的地方,可以试试使用位运算符!
特殊的加减方式
对于变量,除了 x = x + 1 这种赋值方法,你也可以使用类似 x += 1 这种赋值方法,即将运算符提前,这样的写法对于大部分的二目运算符都可以使用。这样写的好处是代码更加简洁,但请确定好运算符之间的运算顺序,否则可能会出现意想不到的结果