跳到主要内容

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
转义字符
转义字符意义转义字符意义
\0Null 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