c 语言简明教程
基本语法
语句
int x = 1;
语句块
{
int x;
x = 1;
}
注释
/* 注释 */
/*
多行注释
*/
// 单行注释
头文件
#include <stdio.h>
变量
命名规则
- 字母,下划线开头;
- 不能以数字开头;
- 不能超过 63 个字符;
- 不能使用保留字和关键字;
声明和赋值
// 先声明再赋值
int num;
num = 42;
// 同时声明并赋值
int num = 42;
作用域
文件作用域
- 声明在文件顶层的;
- 作用域整个文件;
int x = 1;
int main(void) {
printf("%i\n", x);
}
块作用域
- 声明在 内部;
- 在 有效;
int a = 12;
if (a == 12) {
int b = 99;
printf("%d %d\n", a, b); // 12 99
}
printf("%d\n", a); // 12
printf("%d\n", b); // 出错
作用域的优先级
- 若多个作用域具有相同变量;
- 优先使用内层作用域的变量;
{
int i = 10;
{
int i = 20;
printf("%d\n", i); // 20
}
printf("%d\n", i); // 10
}
运算符
算术运算符
算术运算符
int result = 34 * 56; // 乘
int result = 66 / 11; // 除
int result = 26 % 5; // 取余
int result = 3 ** 2; // 幂运算
int result = 1 + 2; // 加
int result = 2 - 1; // 减
赋值运算符简写
i += 3; // 等同于 i = i + 3
i -= 8; // 等同于 i = i - 8
i *= 9; // 等同于 i = i * 9
i /= 2; // 等同于 i = i / 2
i %= 5; // 等同于 i = i % 5
自增自减操作符
// ++x, 先自增再返回值
// --x, 先自减再返回值
// x++, 先返回值再自增
// x--, 先返回值再自减
int num1 = 29;
int num2 = --num1 + 2; // 30
int num3 = num1-- + 2; // 31
关系运算符
运算符 | 机制 |
---|---|
> | 若左操作数大于右操作数返回 true, 反之返回 false |
>= | 若左操作数大于等于右操作数返回 true, 反之返回 false |
<= | 若左操作数小于右操作数返回 true, 反之返回 false |
<= | 若左操作数小于等于右操作数返回 true, 反之返回 false |
== | 若相等返回 true, 反之返回 false |
!= | 若不相等返回 true, 反之返回 false |
逻辑运算符
操作符 | 含义 | 机制 |
---|---|---|
!expr | 非 | 转换为 boolean 并取反 |
expr1 && expr2 | 与 | 一假即假, 全真才真 |
expr1 || expr2 | 或 | 一真即真, 全假才假 |
位运算符
- 略;
逗号运算符
- 从左向右依次运算;
- 返回最后一个表达式;
x = 10, y = 20;
优先级和结合性
- 优先级:不同级运算符的计算顺序;
- 结合性:同级运算符的计算顺序;
控制流程
if
if (expression)
statement
else if (expression)
statement
else
statement
?:
- expression1 为真,返回 expression2,反之返回 expression3;
<expression1> ? <expression2> : <expression3>
switch
switch (grade) {
case 0:
printf("False");
break;
case 1:
printf("True");
break;
default:
printf("Illegal");
}
while
while (expression) {
statement;
}
do..while
do {
statement;
} while (expression);
for
for (initialization; continuation; action){
statement;
}
break
- switch 中断分支;
- 循环跳出循环体;
continue
- 循环进入下一轮循环;
goto
- 跳转至指定位置;
char ch;
top: ch = getchar();
if (ch == 'q')
goto top;
数据类型
字符类型
字符类型
- 单个字符,8 bit;
- 作为整数处理,每个字符对应一个整数;
char a = 'B'; // 等同于 char a = 66;
char b = 'C'; // 等同于 char b = 67;
printf("%d\n", a + b); // 输出 133
转义字符
转义字符 | 意义 | 转义字符 | 意义 |
---|---|---|---|
\0 | Null Byte | \' | 单引号 |
\b | 退格 | \" | 双引号 |
\n | 换行符 | \ | \ |
\f | 换页符 | \XXX | 八进制 |
\r | 回车键 | \xnn | 二位十六进制 |
\t | 水平制表符 |
布尔类型
布尔类型
- 使用 stdbool.h 头文件;
#include <stdbool.h>
bool flag = false;
字面量和字面量后缀
字面量
- 直接出现的值;
字面量后缀
int x = 123L; // int 类型指定 long
int x = 123U; // 指定 unsigned
1.2345e+10F // 指定 float
1.2345e+10L // 浮点数指定 double
数字类型
整数类型
基础
- 16/32/64 位;
// 带正负号, 默认值
signed int a;
// 不带正负号
unsigned int a;
// 位数不多于 int
short int a;
// 位数不少于 int
long int b;
// 位数不少于 long
long long int c;
unsigned short int a;
unsigned long int b;
unsigned long long int c;
极限值
- 头文件 limits.h 中定义了一堆常量;
进制
- 默认十进制;
int a = 012; // 八进制, 相当于十进制的10
int a = 0x1A2B; // 十六进制, 相当于十进制的6699
浮点数类型
- float:32 位;
- double:64 位;
- 至少 13 位有效数字;
float c = 10.5;
double x = 123.456e3;
溢出
溢出
- 向上溢出:大于最大值;
- 向下溢出:小于最小值;
后果
- 最大变最小;
- 最小变最大;
sizeof 运算符
- 返回参数数据类型的字节数量
printf("%zd\n", sizeof(int));
类型转换
隐式转换
赋值运算
- 将右值转换为左值;
- 转换为宽度较大的类型;
- signed < unsigned;
- short < long < long long < float < double;
函数
- 自动转换为函数定义的返回值类型;
显式转换
(unsigned char) ch
long int y = (long int) 10 + 12;
指针
基础
声明指针
int* intPtr;
*
- 获得指针变量指向的内存地址的值;
// *p 为 p 指向的内存地址的值
// 对 *p 赋值即修改对应内存地址的值
void increment(int* p) {
*p = *p + 1;
}
&
- 取出变量所在的内存地址;
int x = 1;
printf("x's address is %p\n", &x);
& 和 *
// 互为逆运算
int i = 5;
if (i == *(&i)) // 正确
void 指针
- 无类型指针;
int x = 10;
void* p = &x; // 整数指针转为 void 指针
int* q = p; // void 指针转为整数指针
指针的运算
指针和整数的加法
- 表示指针的移动;
short* j;
j = (short*)0x1234;
j = j + 1; // 0x1236
指针和指针的减法
- 表示两个指针的距离;
short* j1;
short* j2;
j1 = (short*)0x1234;
j2 = (short*)0x1236;
ptrdiff_t dist = j2 - j1;
printf("%td\n", dist); // 1
指针和指针的比较运算
- 比较内存地址对应的十六进制的大小;
函数
基础
声明函数
int plus_one(int n) {
return n + 1;
}
调用函数
- 传递参数和函数定义参数数量一致;
int plus_one(int n) {
return n + 1;
}
plus_one(2, 2); // 报错
plus_one(); // 报错
传参
值传递
- 若传递参数为变量;
- 传入的使其副本;
引用传递
- 若传递参数为其地址;
- 函数内部即可改变该参数;
函数指针
函数名和函数指针
- c 规定函数名就是指向函数代码的指针;
if (print == &print) // true
调用函数的 n 种写法
// 写法一
print(10)
// 写法二
(*print)(10)
// 写法三
(&print)(10)
函数原型
- 程序开头提前告知编译器函数参数和返回类型;
- 无函数体,可在后面任意位置;
- 可省略参数名;
int twice(int);
int main(int num) {
return twice(num);
}
int twice(int num) {
return 2 * num;
}
可变参数
- 使用 。。。;
- 放在函数结尾;
// va_list 定义可变参数对象
// va_start 取出可变参数
// va_arg 每次调用指向下一个可变参数
// va_end 释放内存
double average(int i, ...) {
double total = 0;
va_list ap;
va_start(ap, i);
for (int j = 1; j <= i; ++j) {
total += va_arg(ap, double);
}
va_end(ap);
return total / i;
}
main()
- 程序的入口函数;
- 默认返回 0 (可省略);
int main(void) {
printf("Hello World\n");
return 0;
}
函数说明符
extern
- 默认值;
- 表明函数在其他文件;
extern int foo(int arg1, char arg2);
int main(void) {
int a = foo(2, 3);
// ...
return 0;
}
static
- 函数只会初始化一次;
void counter(void) {
static int count = 1; // 只初始化一次
printf("%d\n", count);
count++;
}
int main(void) {
counter(); // 1
counter(); // 2
counter(); // 3
counter(); // 4
}
const
- 常量;
// 限制修改 *p
void f(const int* p) {
*p = 0; // 该行报错
}
// 限制修改 p
void f(int* const p) {
int x = 13;
p = &x; // 该行报错
}
// 限制修改 p 和 *p
void f(const int* const p) {
// ...
}
数组
一维数组
// 声明数组
int scores[100];
// 声明并初始化
int a[5] = {22, 37, 3490, 18, 95};
// 为初始化自动赋值为 0
int a[5] = {22, 37, 3490};
int a[5] = {22, 37, 3490 , 0, 0};
多维数组
- 同一维数组;
int board[10][10];
int c[4][5][6];
int a[2][5] = {
{0, 1, 2, 3, 4},
{5, 6, 7, 8, 9}
};
变长数组
- 运行时确定数组长度;
int m = 4;
int n = 5;
int c[m][n];
数组指针
数组地址
- 数组地址连续存储;
- 数组名等同数组起始地址,即指向第一个成员的指针;
int a[5] = {11, 22, 33, 44, 55};
int* p = &a[0];
// 等同于
int* p = a;
函数原型中的数组
// 写法一
int sum(int arr[], int len);
// 写法二
int sum(int* arr, int len);
字符串
声明字符串
- 字符串为 char 类型的数组;
- 数组数组最后一个恒为
\0
; - 双引号中的字符自动视为字符数组;
char localString[10];
char* s = "Hello, world!";
多行字符
// \ 结尾
"hello \
world"
// 多个字面量无间隔或只有空白
char greeting[50] = "Hello, "
"how are you "
"today!";
字符指针和字符数组的区别
- 不能修改字符指针;
- 字面量指向内存的常量区,不可修改;
- 字符数组是编译器分配的内存,可以修改;
- 指针变量可以指向其他字符串;
- 数组变量所在地址无法改变;
char* s = "Hello, world!";
s[0] = 'z'; // 错误
char s[] = "hello";
s = "world"; // 报错
内存管理
API
- malloc():堆分配内存;
- free():释放 malloc() 分配的底层;
- calloc():分配指定类型,指定长度的内存;
- realloc():修改已经分配的内存块大小;
- memcpy():拷贝内存;
- memmove():拷贝内存,允许重叠;
- memcmp():比较内存;
restrict 说明符
- 受限指针;
- 只能由当前指针访问该内存;
int* restrict p;
p = malloc(sizeof(int));
struct 和 typedef
struct
定义
struct fraction {
int numerator;
int denominator;
};
声明并初始化
// 顺序一致, 未初始化自动赋 0
struct car saturn = {"Saturn SL/2", 16000.99, 175};
内存对齐
- struct 使用内存对齐机制;
- 内存占用为属性最大占用内存的倍数;
嵌套
struct species {
char* name;
int kinds;
};
struct fish {
char* name;
int age;
struct species breed;
};
自我引用
struct node {
int data;
struct node* next;
};
值传递
- struct 作为函数参数为值传递;
struct 指针
void happy(struct turtle* t) {
}
happy(&myTurtle);
typedef
- 提高代码可读性
// 基本类型
typedef unsigned char BYTE;
BYTE c = 'z';
// 指针
typedef int* intptr;
int a = 10;
intptr x = &a;
// 函数
typedef signed char (*fp)(void);
Union 和 Enum
Union
- 定义多个数据类型;
- 但是一次只能使用一种;
union quantity {
short count;
float weight;
float volume;
};
Enum
定义 Enum
enum colors {RED, GREEN, BLUE};
printf("%d\n", RED); // 0
printf("%d\n", GREEN); // 1
printf("%d\n", BLUE); // 2
定义 Enum 类型变量
enum colors color;
color = BLUE;
printf("%i\n", color); // 2
自动编号
- 自动从 0 递增;
- 指定值后根据指定值递增;
enum {
A, // 0
B, // 1
C = 4, // 4
D, // 5
};
预处理器
#define
- 全大写;
- 下划线连接;
#define MAX 100
多重替换
#define TWO 2
#define FOUR TWO*TWO
带参数的宏
- 尽量多的使用 ();
- 要不会有奇怪的问题;
#define SQUARE(X) ((X) * (X))
z = SQUARE(2);
io
printf()
基本用法
printf("Hello\nWorld\n");
占位符
printf("There are %i apples\n", 3);
修饰符
// 限定宽度
printf("%5d\n", 123); // 输出为 " 123"
printf("%-5d\n", 123); // 输出为 "123 "
// 正负号
printf("%+d\n", 12); // 输出 +12
printf("%+d\n", -12); // 输出 -12
// 限定小数位数
printf("Number is %.2f\n", 0.5); // 输出 0.50