## 变量与类型
### 整数
C 给我们提供了下列定义整数的类型:
- `char`
- `int`
- `short`
- `long`
通常,你很可能会使用 `int` 保存整数。但是在某些情况下,你或许想在其它三个选项中选取合适的类型。
`char` 类型通常被用来保存 ASCII 表中的字母,但是它也可以用来保存 `-128` 到 `127` 之间的小整数。它占据至少一个字节。
`int` 占据至少两个字节。`short` 占据至少两个字节。`long` 占据至少四个字节。
如你所见,我们并不保证不同环境下的值相同。我们只有一个指示。问题在于每种数据类型中所存储的具体值是由实现和系统架构决定的。
我们保证 `short` 不会比 `int` 长。并且我们还保证 `long` 不会比 `int` 短。
ANSI C 规范标准确定了每种类型的最小值,多亏了它,我们至少可以知道使用某个类型时可以期待的最小值。
如果你正在 Arduino 上用 C 编程,不同的板子上的限制会有所不同。
在 Arduino Uno 开发板上,`int` 占两个字节,范围从 `-32,768` 到 `32,767`。在 Arduino MKR 1010 上,`int` 占四个字节,范围从 `-2,147,483,648` 到 `2,147,483,647`。差异还真不小。
在所有的 Arduino 开发板上,`short` 都占两个字节,范围从 `-32,768` 到 `32,767`。`long` 占四个字节,范围从 `-2,147,483,648` 到 `2,147,483,647`。
### 无符号整数
对于以上所有的数据类型,我们都可以在其前面追加一个 `unsigned`。这样一来,值的范围就不再从负数开始,而是从 0 开始。这在很多情况下是很有用的。
- `unsigned char` 的范围从 `0` 开始,至少到 `255`
- `unsigned int` 的范围从 `0` 开始,至少到 `65,535`
- `unsigned short` 的范围从 `0` 开始,至少到 `65,535`
- `unsigned long` 的范围从 `0` 开始,至少到 `4,294,967,295`
### 浮点数
浮点类型可以表示的数值范围比整数大得多,还可以表示整数无法表示的分数。
使用浮点数时,我们将数表示成小数乘以 10 的幂。
你可能见过浮点数被写成
- `1.29e-3`
- `-2.3e+5`
和其它的一些看起来很奇怪的形式。
下面的几种类型:
- `float`
- `double`
- `long double`
是用来表示带有小数点的数字(浮点类型)的。这几种类型都可以表示正数和负数。
任何 C 的实现都必须满足的最小要求是 `float` 可以表示范围在 10^-37 到 10^+37 之间的数,这通常用 32 位比特实现。 `double` 可以表示一组更大范围的数,`long double` 可以保存的数还要更多。
与整数一样,浮点数的确切值取决于具体实现。
在现代的 Mac 上,`float` 用 32 位表示,精度为 24 个有效位,剩余 8 位被用来编码指数部分。
`double` 用 64 位表示,精度为 53 个有效位,剩余 11 为用于编码指数部分。
`long double` 类型用 80 位表示,精度为 64 位有效位,剩余 15 位被用来编码指数部分。
```c
#include <stdio.h>
int main(void) {
printf("char size: %lu bytes\n", sizeof(char));
printf("int size: %lu bytes\n", sizeof(int));
printf("short size: %lu bytes\n", sizeof(short));
printf("long size: %lu bytes\n", sizeof(long));
printf("float size: %lu bytes\n", sizeof(float));
printf("double size: %lu bytes\n", sizeof(double));
printf("long double size: %lu bytes\n", sizeof(long double));
}
```
```
char size: 1 bytes
int size: 4 bytes
short size: 2 bytes
long size: 8 bytes
float size: 4 bytes
double size: 8 bytes
long double size: 16 bytes
```
## 常量
咱们现在来谈谈常量。
常量的声明与变量类似,不同之处在于常量声明的前面带有 `const` 关键字,并且你总是需要给常量指定一个值。
就像这样:
```c
const int age = 37;
```
这在 C 中是完全有效的,尽管通常情况下将常量声明为大写,就像这样:
```c
const int AGE = 37;
```
虽然这只是一个惯例,但是在你阅读或编写 C 程序时,他能给你提供巨大的帮助,因为它提高了可读性。大写的名字意味着常量,小写的名字意味着变量。
常量的命名规则与变量相同:可以包含任意大小写字母、数字和下划线,但是不能以数字开头。`AGE` 和 `Age10` 都是有效的变量名,而 `1AGE` 就不是了。
另一种定义常量的方式是使用这种语法:
```c
#define AGE 37
```
在这种情况下,你不需要添加类型,也不需要使用等于符号 `=`,并且可以省略末尾的分号。
C 编译器将会在编译时从声明的值推断出相应的类型。
## 运算符
C 给我们提供了各种各样的运算符,我们可以用来操作数据。
特别地,我们可以识别不同分组的运算符:
- 算术运算符
- 比较运算符
- 逻辑运算符
- 复合赋值运算符
- 位运算符
- 指针运算符
- 结构运算符
- 混合运算符
在这一节中,我们将用两个假想的变量 `a` 和 `b` 举例,详细介绍所有这些运算符。
为了简单起见,我将不会介绍位运算符、结构运算符和指针运算符。
### 算术运算符
我将把这个小型分组分为二元运算符和一元运算符。
二元操作符需要两个操作数:
|操作符|名字|示例|
|---|---|---|
|`=`|赋值|`a = b`|
|`+`|加|`a + b`|
|`-`|减|`a - b`|
|`*`|乘|`a * b`|
|`/`|除|`a / b`|
|`%`|取模|`a % b`|
一元运算符只需要一个操作数:
| 运算符 | 名字 | 示例 |
| ------ | ------ |:--------------:|
| `+` | 一元加 | `+a` |
| `-` | 一元减 | `-a` |
| `++` | 自增 | `a++` or `++a` |
| `--` | 自减 | `a--` or `--a` |
`a++` 与 `++a` 的区别在于:`a++` 在使用 `a` 之后才自增它的值,而 `++a` 会在使用 `a` 之前自增它的值。
例如:
```c
int a = 2;
int b;
b = a++ /* b 为 2,a 为 3 */
b = ++a /* b 为 4,a 为 4 */
```
### 比较运算符
|运算符|名字|示例|
|---|---|---|
|`==`|相等|`a == b`|
|`!=`|不相等|`a != b`|
|`>`|大于|`a > b`|
|`<`|小于|`a < b`|
|`>=`|大于等于|`a >= b`|
|`<=`|小于等于|`a <= b`|
### 逻辑运算符
- `!` 非(例如:`!a`)
- `&&` 与(例如:`a && b`)
- `||` 或(例如:`a || b`)
这些运算符在使用布尔值时非常有用。
<br>
### 复合赋值运算符
当赋值与算术运算同时进行时,这些运算符非常有用。
| 运算符 | 名字 | 示例 |
| ---- | ----- | -------- |
| `+=` | 加且赋值 | `a += b` |
| `-=` | 减且赋值 | `a -= b` |
| `*=` | 乘且赋值 | `a *= b` |
| `/=` | 除且赋值 | `a /= b` |
| `%=` | 求模且赋值 | `a %= b` |
<br>
### 三目运算符
三目运算符是 C 中唯一一个使用三个操作数的运算符,并且它是表达条件的简便方法。
它看起来长这样:
`<条件> ? <表达式> : <表达式>`
示例:
若 `a` 的值为 `true`,就执行语句 `b`,否则执行语句 `c`。
三目运算符的功能与 if/else 条件语句相同,但是它更短,还可以被内联进表达式。
```c
#include <iostream> //编译预处理命令
using namespace std; //使用命名空间
int main() //主函数
{
int score = 75; // 假设这是用户的分数
const char* result = (score >= 60) ? "通过" : "未通过";
printf("考试结果:%s\n", result);
}
```
在这个例子中:
- `<条件>` 是 `(score >= 60)`,检查用户分数是否达到及格线。
- 第一个 `<表达式>` 是 `"通过"`,当条件为真时的返回值。
- 第二个 `<表达式>` 是 `"未通过"`,当条件为假时的返回值。
因此,如果 `score` 确实大于等于60,`result` 就会被赋值为 `"通过"`,否则会被赋值为 `"未通过"`。最后,使用 `printf` 打印出考试结果。
### sizeof
`sizeof` 运算符返回你传入的操作数的大小。你可以传入变量,或者甚至是类型也可以。
使用示例:
```c
#include <stdio.h>
int main(void) {
int age = 37;
printf("%ld\n", sizeof(age));
printf("%ld", sizeof(int));
}
```
### 运算符优先级
对于所有的这些运算符(以及我们还没有在本文中介绍的其它运算符,包括位运算符、结构运算符和指针运算符),我们在单个表达式中一起使用它们时必须要留意。
假如我们有这个运算:
```c
int a = 2;
int b = 4;
int c = b + a * a / b - a;
```
`c` 的值是多少?我们在执行乘和除之前有进行加法操作吗?
这里是给我们解惑的一组规则。
按照顺序,优先级从低到高:
- 赋值运算符 `=`
- 二元运算符 `+` 和 `-`
- 运算符 `*` 和 `/`
- 一元运算符 `+` 和 `-`
运算符还具有关联规则,除了一元运算符和赋值运算符之外,该规则总是从左到右的。
在:
```c
int c = b + a * a / b - a;
```
中,我们首先执行 `a * a / b`,由于是从左到右的,我们可以拆分为 `a * a` 与其结果 `/b`:`2 * 2 = 4`,`4 / 4 = 1`。
然后我们可以进行加法操作和减法操作:4 + 1 - 2。`c` 的值是 `3`。
然而,在所有的示例中,我都想确保你意识到你可以使用括号让任何相似的表达式更易读和易理解。
括号的优先级比其它任何运算符都要高。
上述示例表达式可以被重写为:
```c
int c = b + ((a * a) / b) - a;
```
并且我们不必考虑太多。
## 条件语句
任何编程语言都给程序员提供了进行选择的能力。
我们想要在一些情况下进行 X,而在其它情况下进行 Y。
我们想检查数据,根据数据的状态做选择。
C 给我们提供了两种方式。
第一种方式是带 `else` 的 `if` 语句,第二种是 `switch` 语句。
### if
在 `if` 语句中,你可以在检查到条件为 true 的时候,执行花括号内的代码块:
```c
int a = 1;
if (a == 1) {
/* 进行一些操作 */<