Skip to content

The C Programming Language

工作流

预处理 -> 编译 -> 链接 ->

基本语法

易错易忘

  • 复合语句在语法上被认为是一条语句
  • ; 空语句
  • 函数类型未加显式说明,则函数的隐含类型为 int
  • %% 可输出百分号
  • 赋值语句的结果是右侧表达式的值
  • 函数分为库函数和自定义函数
  • , 分隔可以在一行执行多条指令
  • abs() 求整型的绝对值,fabs() 求浮点型的绝对值
int x=0, y=0, z=0;
z = (x==1) && (y=2);
printf("%d ", y);

// &&惰性,当前一个条件为真,执行赋值操作
<<<0
int i=10;
long long t = sizeof(i++);
printf("%d", i);

// sizeof()只是获取值,不影响实际值
<<<10
int i,x,y;
i=x=y=0;
do {
    ++i;
    if ( i%2 ) 
        x+=i, 
    i++;
    y +=i++;
} while ( i<=7 );
printf("%d %d %d", i, x, y);

// 注意逗号
<<<9 1 20

类型转换

  1. 赋值操作
  2. 算术运算 整数提升,较低类型的操作数会被提升到较高类型
  3. 表达式求值
  4. 函数调用 实参的类型被转换为形参的类型
  5. 强制类型转换

位运算

符号 描述 运算规则 常用功能
& 1 1,反之为 0 单元清零,取数的指定位,判断奇偶
| 1 1,反之为 0 对一个数据的某些位置设置为 1
^ 异或 相同为 0,相异为 1 1 反转指定位,交换两数
~ 取反
<< 左移 左移若干位,高位丢弃,低位补 0 左移一位,相当于乘 2
>> 右移 右移若干位,无符号数高位补 0,有符号数据编译器定 2 右移一位,相当于除以 2

符号优先级

参考:ZhouTimeMachine/Note C 语言运算符优先级

优先级 运算符 名称或含义 结合性 / 目数
1 [] () 数组下标 圆括号
1 -> . 成员选择(指针) 成员选择(对象)
2 - ++ -- 取负 自增 自减 /
2 (类型) 强制类型转换
2 * & 取值 取地址 /
2 ! ~ 逻辑非 按位取反
2 sizeof 长度
3 * / % 乘 除 取模 /
4 + - 加 减 /
5 << >> 左移 右移 /
6 >(=) <(=) 大于(等于) 小于(等于) /
7 == != 等于 不等于 /
8 / 9 / 10 & ^ \| 按位与 按位异或 按位或 /
11 / 12 && \|\| 逻辑与 逻辑或
13 ?: 条件运算符 /
14 各种赋值
15 逗号运算符

逻辑:非 > > 或 位运算:反 > 左移/右移 > 与 > 异或 > 或 结构体指针:指向/结构 > 自增/自减 > 指针*

自增自减

  • 自增自减必须是可变值,1++(-x)++等都不行
  • i++的值是i,使用后才自增,++i相反先自增

输入与输出

scanf/printf

  • 不会自动换行
  • %d 十进制整型数 %ld 长整型数
  • %x 十六进制数 %o 八进制数
  • %-4d 整型数打印,占四位左对齐
  • %02d 整型数打印,不足的用 0 填充
  • %f float %lf double
  • %6.2f 浮点数打印,小数点后有两位小数(四舍五入得到)
  • %c 字符 %s 字符串
  • scanf 无法接收带有空格字符串
  • 八进制的前缀是 0,十六进制的前缀是 0x
int k=11;
printf("k=%d,k=%o,k=%x\n",k,k,k);
>>>k=11,k=13,k=b

getchar/putchar

#include <stdio.h>
int main(){
    int c;
    while ((c=getchar()) != EOF)
        putchar(c);
}
/* 将输入复制到输出 */

puts/gets

int buf[100];
gets(buf); //等价于gets(&buf[0]);

循环与判断

switch

  • case 必须互不相同且与表达式类型一致
  • 执行 case 符合以后所有的语句(break 以避免不必要结果)
  • default 可放在任意位置

数据类型

整型 & 浮点型

无符号整型
unsigned short sht=0;
sht--;
printf("%d",sht);

>>>65535
有符号整型
正数:
  • 原码、反码、补码相同
负数:
  • 原码由对应正数原码最高位取反得到
  • 反码由原码按位取反(符号位除外)得到
  • 补码由原码取反加 1 得到

字符

//表示字符A,ASCLL对应八进制101,十六进制41
//'A', '\101', '\x41'

强制类型转换

(type_name) expression
  • 强制类型转换运算符的优先级大于除法
  • 浮点型转整型舍去小数部分

常量与变量

常量

#include <stdio.h>

#define IN 1
#define OUT 0

int main(){
    int c,nl,nw,nc,state;

    state=OUT;
    nl=nw=nc=0;
    while((c=getchar())!=EOF){
        ++nc;
        if (c=='\n')
            ++nl;
        if (c==' ' || c=='\n' || c=='\t')
            state=OUT;
        else if (state==OUT){
            state==IN;
            ++nw;
        }
    }
    printf("%d %d %d\n",nl,nw,nc);
    return 0;
}
/* 统计输入的行数,单词数与字符数 */
  • 常量名大写,与变量区分

变量

  • 全局变量和静态变量默认值为 0
  • 局部变量初始值未定义

类型、运算、表达式

++ 后缀 使用后自增 ++前缀 使用前自增 函数声明时()中无参数表示参数未知,并非无参数(无参数:void f(void))

数组

一维数组初始化

全局或静态变量
  • 未显式地初始化,自动赋予初始值 0
  • 不完全初始化,未赋值元素自动赋予初始值 0
局部变量
  • 未初始化,初始值未知,可能为任意值
  • 不完全初始化,未赋值元素自动赋予初始值 0

二维数组初始化

  • 列数必须给出
  • 每行一个 {},逗号分隔
  • 如果省略,表示补零

集成初始化(C99 only)

  • [n] 在初始化数据中给出定位
  • 没有定位的数据接在前面位置数据的后面
  • 其他位置的值补零
  • 适用于初始数据稀疏的矩阵

数组与指针

  • 函数参数表中的数组实际是指针
  • sizeof(a)==size(int*)
  • 可以用数组的运算符 [] 进行运算
#include <stdio.h>
int main(){
    int a[3]={1,2,3},sum=0,i;
    for (i=0;i<3;i++)
        sum+=0[a+i];
    printf("%d",sum);
} 
//加0可以让数组名放在里面

字符与字符串

char %d 读返回 ASCII 码 C语言中的字符串应该理解为字符数组

字符串初始化

%以下都可以实现字符串的初始化
static char s[6] = {'H', 'a', 'p', 'p', 'y', '\0'};
static char s[6] = {'H', 'a', 'p', 'p', 'y'};
static char s[6] ="Happy";
static char s[6] ={"Happy"};
static char s[6] = {'H', 'a', 'p', 'p', 'y', 0};

sizeof strlen

  • strlen() 是计算字符串第一个 '\0' 前的字符数量的字符串函数
  • sizeof() 是计算存储空间的运算符
    char a[]="abc";
    char b[]={'a','b','c'};
    pirntf("%d %d",sizeof(a),sizeof(b));
    //"abc"相当于{'a','b','c','d','\0'},故sizeof(a)=4,sizeof(b)=3
    

字符串函数

  • strcmp() 比较的是字符的字典顺序 小于返回-1,等于返回0,大于返回1

指针

用途

  • 函数返回多个值
  • 传入较大的数据时用作参数
  • 传入数组后对数组做操作
  • 用函数修改不止一个变量
  • 动态申请内存

比较

  • 可以比较指向同一数组的指针、同一对象的成员指针、同一内存块的指针
  • 所有指针都可以与空指针比较
  • 指针相减不是地址相减 , 而是指针间元素个数

注意

//p+n=p+n*sizeof(*p)

结构体

  • 定义结构体时需要分号
  • -> 称为指向运算符
  • . 称为分量 / 成员运算符
//访问结构体成员
//struct_point->member
//(*struct_point).member

算法

辗转相除法

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

搜索和排序

时间复杂度

排序算法 最佳情况 最坏情况 平均情况
冒泡排序 \(O(n)\)(优化后) \(O(n^2)\) \(O(n^2)\)

冒泡排序(Bubble Sort

通过比较相邻元素的大小,将较大(或较小)的元素交换到右边(或左边,从而实现排序。

void bubble_sort(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {
        for (int j = 0; j < n-i-1; j++) {
            if (arr[j] > arr[j+1]) {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr, n);
    printf("Sorted array is: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

选择排序(Selection Sort

在未排序部分中找到最小(或最大)的元素,将其放到已排序部分的末尾,然后递归进行。

void selection_sort(int arr[], int n) {
    for (int i = 0; i < n-1; i++) {
        int min_idx = i;
        for (int j = i+1; j < n; j++) {
            if (arr[j] < arr[min_idx]) {
                min_idx = j;
            }
        }
        int temp = arr[i];
        arr[i] = arr[min_idx];
        arr[min_idx] = temp;
    }
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);
    selection_sort(arr, n);
    printf("Sorted array is: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

插入排序(Insertion Sort

将未排序的元素插入到已排序的部分中,直到所有元素都排好序。插入排序的时间复杂度也为 ,但是对于小型数据集来说效率较高。

void insertion_sort(int arr[], int n) {
    for (int i = 1; i < n; i++) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

int main() {
    int arr[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr) / sizeof(arr[0]);
    insertion_sort(arr, n);
    printf("Sorted array is: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

快速排序(Quick Sort

通过选择一个基准值,将数组分为两部分,左边部分的元素都小于基准值,右边部分的元素都大于基准值,然后对左右两部分分别递归进行快速排序。快速排序的平均时间复杂度为 O(nlogn),但最坏情况下可能达到

归并排序(Merge Sort: 将数组分为两个子数组,对每个子数组进行排序,然后合并两个已排序的子数组以产生最终排序结果。归并排序的时间复杂度始终为

堆排序(Heap Sort: 将数组视为二叉树,然后将其转换为最大堆或最小堆,然后依次将堆顶元素与堆的最后一个元素交换,然后对剩余元素进行堆调整。堆排序的时间复杂度为

计数排序(Counting Sort: 首先统计数组中每个元素的出现次数,然后根据元素的值和出现次数将其放置到正确的位置上。计数排序的时间复杂度为

基数排序(Radix Sort: 将整数按照位数进行分桶排序,从低位到高位依次进行排序,直到所有位数都排序完成。基数排序的时间复杂度为

程序结构

预处理

define 宏名标识符 宏定义字符串

  • C 语句,无分号
  • 作用域:定义书写处 -> 文件尾 /#undef

文件操作

  • typedef 自定义类型

文件类型

  • C 语言中的文件时数据流
  • 文件两种数据类型 ASCLL 码字符流 和 二进制码
  • 文件缓冲器 FILE 结构类型
  • fopen() 使文件指针与相应实体文件对应起来,程序通过文件指针 fp 操作磁盘文件
  • 执行成功返回文件指针 FILE
  • 否则返回空指针 NULL| 使用方式 | 含义 | | :------: | :-------------: | | r | 打开只读 | | w | 建立只写新文件 | | a | 打开添加写文件 | | r+ | 打开读/写文件 | | w+ | 打开读/写新文件 | | a+ | 打开读/写文件 |
  • fclose(FILE *fp) 将缓冲区数据写入磁盘,释放文件缓冲区单元和 FILE 结构体
  • 0 正常关闭
  • 0 无法正常关闭
  • fscanf( 文件指针,格式字符串,输入表 ) 格式化文件输入函数
  • fprintf(,格式字符串,输出表 ) 格式化输出函数
  • fgets(char *s,int n,FILE *fp) fp 指示文件读取字符串 s,最多读取 n-1 个字符
  • 自动添加 \0
  • 成功返回 s,否则返回 NULL
  • fputs() 字符串写
  • 不写入 \0
  • 成功返回最后一个字符 / 非负整数 , 否则返回 EOF
  • fgetc() 字符读
  • fputc() 函数用于向指定文件写入单个字符
  • feof() 函数用于检测文件指针是否已经到达文件末尾。
  • 如果文件指针已经到达文件末尾,则 feof() 函数返回 1,否则返回 0
  • fseek() 文件定位
  • ferror() 检测文件读写出错函数

  • rewind() 将文件内部的指针重新指向一个文件的开头

  • ftell() 获取当前文件指针的位置
  • clearerr() 清除

杂项

动态内存分配

#include <stdlib.h>

  1. 异或的一些性质

    1. 交换律
    2. 结合律
    3. x^0=x, x^x=0
    4. 自反性:abb=a^0=a

  2. 有的补符号位 ( 算术右移 ),有的补 0( 逻辑右移 )

评论区

如果有什么问题或想法,欢迎大家在下方留言~