基本语法
这里是 C 的基本语法规则
注释
// 单行注释,只能注释单行
/*
多行注释
可以注释多行内容
*/
如果在单行注释后面加一个末尾\
,会产生行合并,即后面的一行都会被当作注释,可以使用多行注释解决
int main(void) {
// 产生行合并 \
printf("hello");
/* 不会产生行合并 */ \
printf("world"); // world
return 0;
}
代码块和语句
C 采用{}
来区分代码之间的层次,每条语句必须以;
结束
标识符
- 由英文字母,数字和下划线组成,但第一个字符不能是数字
- 不能是 C 中的关键字和保留字
- 区分大小写
变量和常量
C 是一个静态类型语言,必须声明变量的数据类型,但是并没有强制初始化。如果不赋值初始化,访问变量会得到一个无法估测的数据,通过=
赋值运算符来初始化或修改变量的值
int foo; // 声明
int bar = 33; // 声明同时初始化
int foobar, barbaz; // 同时声明多个变量
foo = 11; // 第一次赋值即初始化
foo = 22; // 再次赋值即修改
foo = baz = 44; // 连续赋值,从右向左结合
C 用const
声明只读变量,声明同时必须初始化
const int pi = 3.14;
pi = 3.1415; // error,只读变量是不可以被修改的
在函数或块中声明的变量称为局部变量,反之为全局变量
提示
对于全局变量来说,如果没有初始化,是可以重新进行声明的
常数即一眼就能看出它的值,比如3.14
就是一个常数
// 整数常数
1;
7.; // 整型是可以省略小数点的,也可加上
0b1111; // 二进制
027; // 八进制
0x26; // 十六进制
// 浮点常数
10.2f; // 在 C 中默认是双精度常数,可以通过在常数后加 `f` 符号表示单精度
3.14; // 双精度常数
.7; // 如果整数为 0 可以省略不写
4.5e0; // 指数表示形式,大写 E 和小写 e 都可以,e 后面必须是整数,且前面必须有一个数字
// 字符常数
'a';
'3';
'\n'; // 转义字符
'啊'; // error,在 c 里面中文字符不算一个字符,必须是 ASCII 码表上的字符
// 字符串常数
"h";
"hello";
"你好"; // 字符串是可以使用中文的
数据类型
C 有 5 种数据类型:整型,浮点型,指针,聚合类型,枚举
- 整型
- 短整型(short):占 2 byte,表示的整数范围较小
- 整型(int):占 4 byte,表示的整数范围较大
- 长整型(long):占 4 byte,表示的整数范围最大
- 浮点型
- 单精度(float):占 4 byte,小数点后面的精度相对较小
- 双精度(double):占 8 byte,小数点后面的精度相对较大
- 长双精度(long double):
- 字符型(char):占 1 byte
- 聚合类型
- 数组
- 结构体
- 共用体
- 指针
- 枚举
不同进制的整数表达方式
- 二进制用
0b
表示 - 八进制用
0
表示 - 十进制不需要添加任何额外符号
- 十六进制用`0x表示
提示
字符在计算机中是按字符的 ASCII 值存储的,本质是个整数,根据格式占位符可得到不同的的显示:%c
按字符输出,%d
按整数输出,如果对字符进行数值运算,实际上是算的 ASCII 码中的对应的数值
转义字符
字符也可以用\
开头的特殊序列表示
控制符 | 功能 |
---|---|
\a | 警报 |
\b | 退格 |
\n | 换行 |
\t | 空格 |
\\ | 斜杠 |
' | 单引号 |
" | 双引号 |
\ddd | 1-3 位 8进制表示的字符(用数字替换d) |
\xhh | 1-2 位 16进制表示的字符(x是固定的只需用数字替换h) |
类型检测
C 可以使用sizeof
来判断数据类型,它会返回该数据类型的字节数,但这是在编译器完成的,不能在运行时动态监测数据类型
类型转换
- 自动类型转换:编译器根据上下文环境自动判断,通常转换为范围更大的类型
- 强制类型转换:显式告诉编译器怎么转换数据类型
double foo;
int bar = 3;
foo = bar; // 发生了自动类型转换,int => double
printf("foo = %f", foo); // 输出 3.000000
bar = 0.123456; // 将浮点型常量赋值给整型变量会舍去精度
printf("bar = %d", bar); // 输出 0
提示
对于printf()
函数来说,任何小于int
类型的都会被转换成int
,float
会被自动转换为double
,但是scanf()
函数不会,scanf()
必须要知道输入的数据类型,比如short
,就要用%hd
类型别名
typdef
关键字用于将已知的数据类型定义为新的名字,常用于结构体和复杂的类型声明当中
typedef int SCORE;
int main(void)
{
SCORE s1 = 60; // 相当于 int s1 = 60;
SCORE s2 = 70;
return 0;
}
不仅如此,还可以声明多个别名
typedef int foo, bar;
int main(void)
{
foo s1 = 60; // 相当于 int s1 = 60;
bar s2 = 70; // 相当于 int s2 = 70;
return 0;
}
警告
不能在进行类型别名声明的同时定义变量
运算符
表达式由运算符和运算对象组成,运算对象可以是常量、变量、函数等,或者是多者的结合,但是最重要的一点是表达式一定是有一个结果值
数学运算
运算符 | 描述 |
---|---|
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
% | 取余 |
赋值
运算符 | 描述 |
---|---|
= | 赋值运算符 |
+= | 加且赋值运算符,右操作数和左操作数相加并赋值给左操作数 |
-= | 减且赋值运算符,右操作数和左操作数相减并赋值给左操作数 |
*= | 乘且赋值运算符,右操作数和左操作数相乘并赋值给左操作数 |
/= | 除且赋值运算符,右操作数和左操作数相除并赋值给左操作数 |
比较
运算符 | 描述 |
---|---|
== | 判断相等 |
!= | 判断不相等 |
> | 判断左操作数是否大于右操作数 |
< | 判断左操作数是否小于右操作数 |
>= | 判断左操作数是否大于等于右操作数 |
<= | 判断左操作数是否小于等于右操作数 |
逻辑运算
运算符 | 描述 |
---|---|
&& | 逻辑与,两个操作数都非 0,则为真 |
|| | 逻辑或,两个操作数有一个非 0,则为真 |
! | 逻辑非,逆转操作数的逻辑状态,真则为假,假则为真 |
指针运算
运算符 | 描述 |
---|---|
& | 取地址 |
* | 取引用的对象 |
其他运算
运算符 | 描述 |
---|---|
++ | 自增 |
-- | 自减 |
, | 从左往右计算,将最右边的结果返回 |
提示
关系运算符和逻辑运算符都会产生 0 或 1 ,在 C 中,任何非 0 的值就为真
优先级和结合性
优先级决定谁先计算,结合性指的是当算子两边的运算符优先级别相同时,则按照结合性来确定哪个表达式谁先计算
- ! > 算数 > 关系 > 逻辑 > 赋值
- 单目 > 双目
流程控制
C 默认的执行结构,是从上到下逐行逐句执行
int main(void){ // 从函数代码块开始依次执行语句
int a;
a = 2;
return 0; // 当函数遇到 return 语句时会立即结束运行
}
条件分支
通过if
关键字来定义,条件满足则执行代码块中的语句
int age;
scanf("%d", &age);
if (age >= 18){
printf("你已经成年啦!");
}
if
也可以包含一个可选的case
块,如果条件不成立,就会执行case
代码块中的语句
int age;
scanf("%d", &age);
if (age >= 18){
printf("你已经成年啦!");
} else {
printf("你还没成年呢!");
}
在这个基础上还可以使用多重if else
实现更多分支
int score;
scanf("%d", &score);
if (score >= 90){
printf("优秀");
} else if (score >= 80){
printf("良好");
} else if (score >= 60){
printf("及格");
} else {
printf("不及格");
}
三元运算符是另一种条件运算,即通过?
来构成条件选择,和if else
差不多,如果满足就执行第一个结果,否则就执行另外一个结果
int a = 3, b = 4, c;
c = a > b ? 5 : 6; // 如果 a 大于 b 为真,则将执行第一个表达式,否则执行另一个表达式
相对于if
来说,switch
可以更方便的控制多个分支的流程控制,和if
不同的是,switch
必须精确的匹配一个正确的值才能执行相应的case
,而且会从当前case
一直执行下去,所以需要给每个case
添加break
语句来打断后续的执行,当不匹配任何case
时就执行default
中的语句
int week;
scanf("%d", &week);
switch (week){
case 1:
printf("星期一");
break;
case 2:
printf("星期二");
break;
case 3:
printf("星期三");
break;
case 4:
printf("星期四");
break;
case 5:
printf("星期五");
break;
case 6:
printf("星期六");
break;
case 7:
printf("星期天");
break;
default:
printf("error");
}
循环
C 有while
,do...while
,for
三种循环结构
while
常用于已知的循环次数
如果想要打印 5 遍:hello,world!
,如果不用循环:
printf("hello,world!");
printf("hello,world!");
printf("hello,world!");
printf("hello,world!");
printf("hello,world!");
如果要打印 100 遍,还需要这个写法吗,而用while
就可以这么写:
int i = 0;
while (i++ < 100) {
printf("hello,world!");
}
循环一定要有终止条件,否则会导致死循环产生
死循环
循环一直执行,程序无法终止
do while
循环不管条件是否满足,都会先执行一次才会判断条件
int i = 0;
do {
printf("hello,world!");
i++;
} while (i < 0);
警告
不能忘记这个分号
for
通常用于循环次数不确定的情况
for (expression 1; 判断条件; expression 2){
// code
}
执行流程:
- 执行
expression 1;
,并且只会被执行一次 - 判断条件是否为真
- 为真则执行代码块中的代码,然后执行
expression 2
,重复 2 - 3 步的操作 - 为假则立即结束整个循环
- 为真则执行代码块中的代码,然后执行
expression 1
通常用于初始化变量,可以省略,判断条件用于决定循环体是否执行,expression 2
用于控制循环的终止条件,也可以省略
提示
只保留两个;
时,产生死循环
有时候不需要循环到所有次数才终止,而是在达到某个条件后手动终止整个循环,C 提供了两种打断方式:
- 当遇到
break
语句时,立即退出离break
语句最近的一层循环,不继续下一次循环 - 当遇到
continue
语句时,立即退出当前次循环,继续下一次循环
枚举类型
枚举类型是 C 提供的一种特殊类型,通常用于一些有限值范围的类型,用enum
关键字声明,必须在函数外部定义,每一个取值都会被当作一个只读变量,默认第一个变量的取值为 0,之后的每一个变量取值会依次递增,如果给某一个位置的成员主动赋值,之后的每一个变量会在这个取值的基础上自增
enum en {
foo,
bar
};
int main(void){
printf("%d", foo); // 0
printf("%d", bar); // 1
return 0;
}
对于枚举类型的成员来说,只能在定义的时候赋值,且只能够赋值整型数据,也可以是字符型(因为字符型在内存中是以数字存储的),访问成员时也只需要使用成员标识符即可
也可以通过枚举类型来定义一个普通变量或指针变量,普通变量也是只读的,最好在定义是初始化,否则会是一个垃圾数据
enum en {
foo,
bar
};
int main(){
enum en baz = 1;
enum en *qux = &baz;
printf("%d", baz); // 1
printf("%d", &qux); // 1
return 0;
}