C语言相关

查看相关知识

14.预处理指令和多文件编程

查看相关知识

(1)预处理指令

  • #开头的都是预处理指令,在预处理阶段会处理的指令

  • 预处理—gcc -E xxx.c -o xxx.i

    • #define预处理指令可以用来定义宏
    • 格式:#define 宏名称 (宏值)
    • 宏名通常都是由大写英文字符构成,宏名不可包括空格;用宏给数字命名时,不可使用赋值操作符,不自增自减
    • 宏能够给表达式命名,宏的参数可以用来表示表达式的未知数字;格式:#define 宏名称(参数列表) (宏值表达式)
    • 宏只检查参数个数,不检查参数类型
    • 可以在编译命令里使用-D选项指定宏所代表的数字;由于有些数字在编写程序时无法确定,只有在编译的时候才知道,这个时候需要在程序里使用宏名称代表它们,然后再编译时用数字替换这些宏
    • 宏操作符

      • #是一个宏操作符,能够将宏的参数转换成字符串
      • ##是一个宏操作符,可以将一个代表标识符的参数和其他内容连接为一个新的标识符
      • 编译器内置宏—预定义宏;默认已经定义好的宏,直接使用即可,如下表

    | 宏 | 占位符 | 含义 |
    | —————— | ——— | ——————— |
    |FILE| %s | 所在文件名 |
    |LINE| %d | 所在行号 |
    |FUNCTION| %s | 所在函数名 |
    |func| %s | 所在函数名 |
    |DATE| %s | 编译该文件日期 |
    |TIME| %s | 编译该文件时间 |

  • 案例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    /********************************/
    #include<stdio.h>
    #define PI (3.14)
    int main(void){
    int r=10;
    printf("周长=%lg\n",2*PI*r);
    return 0;
    }
    /******************************/
    #include<stdio.h>
    #define SQUARE(x) ((x)*(x))
    #define SQUARE1(x) (x*x)//不加括号会导致一定逻辑错误
    #define SUB(x,y) ((x)-(y))
    #define PRINT(N) (printf(#N"=%d\n",N))
    #define ID(N) (id##N)
    int main(void){
    printf("%d\n",SQUARE(100));//10000
    printf("%lg\n",SQUARE(5.6));//31.36
    printf("%d\n",SQUARE(15+5));//400
    printf("%d\n",SQUARE1(15+5));//95
    printf("99-76=%d\n",SUB(99,76));//96-76=23
    int a=100,b=20;
    printf("%d-%d=%d\n",a,b,SUB(a,b));//100-20=80
    PRINT(a);//(printf("a""=%d\n",N)//a=100
    int ID(1)=100,ID(2)=101;
    printf("id1=%d,id2=%d\n",id1,id2);//id1=100,id2=101
    return 0;
    }
    /**********************************/
    #include<stdio.h>
    int main(void){
    int arr[SIZE];
    for(int i=0;i<SIZE;i++){
    arr[i]=i+100;
    }
    for(int i=0;i<SIZE;i++){
    printf("%d ",arr[i]);
    }
    printf("\n");
    printf("%s\n",TODAY);
    }
    /*************命令行**************************************/
    gcc -DSIZE=10 define2.c -o define2
    gcc -DSIZE=10 -DTODAY=\"Friday\" define2.c -o define2 //字符的输入需要注意
    /*******************************************************/
    #include <stdio.h>
    int main ()
    {
    printf("File :%s\n", __FILE__ );
    printf("Date :%s\n", __DATE__ );
    printf("Time :%s\n", __TIME__ );
    printf("Line :%d\n", __LINE__ );
    return(0);
    }
  • 条件编译—-条件成立编译,不成立不编译;条件编译应用:一套代码可以适应不同的硬件平台,根据硬件平台生成代码;案例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    #include<stdio.h>
    #define A (1)
    #define B (10)
    #define C (100)
    #define D (1000)
    int main(void){
    #if A==1
    printf("1\n");
    #endif

    #if B==1
    printf("2\n");
    #else
    printf("3\n");
    #endif

    #ifdef C
    printf("4\n");
    #else
    printf("5\n");
    #endif

    #ifndef C
    printf("6\n");
    #else
    printf("7\n");
    #endif

    #if D==100
    printf("8\n");
    #elif D==1000
    printf("9\n");
    #else
    printf("10\n");
    #endif
    return 0;
    }

(2)多文件编程

1.单文件程序改造多文件程序步骤

  • 把所有函数分散在多个不同的源文件里(主函数main.c)
  • 为每个源文件编写配对的以.h作为扩展名的头文件(主函数不需要),头文件使用的宏名称必须根据文件名称变化得到,例如a.h→__A__H
  • 为所有源文件使用#include预处理指令包含必要的头文件

2.头文件卫士

1
2
3
4
#ifdef 宏名称
#define 宏名称
头文件内容
#endif
1
2
3
4
#ifndef __SWAP__H
#define __SWAP__H
int swap(int*,int*);
#endif

3.多文件编译

快捷指令:vim xxx xxx xxx 同时打开多个文件;使用gt在多个文件之间切换

1
gcc main.c swap.c -o swap

4.Makefile

  • 可以将多文件程序的编译步骤记录在Makefile文件中,使用make工具按照Makefile文件里记录的步骤完成编译

  • Makefile文件每个编译命令都不可使用空格字符而应该使用tab键

  • Makefile是一个文本文件

    1
    2
    3
    4
    5
    6
    7
    8
    目标:依赖文件
    由依赖文件生成目标的命令
    main:main.o cal.o
    gcc main.o cal.o -o main
    main.o:main.c
    gcc -c main.c -o main.o
    cal.o:cal.c
    gcc -c cal.c -o cal.o
  • .c文件得到对应的.o文件的小技巧

    1
    2
    3
    4
    5
    上述代码可缩短为以下代码
    main:main.o cal.o
    gcc main.o cal.o -o main
    %.o:%.c
    gcc -c $< -o $@
  • 伪目标即没有依赖的目标,终端输入make clean指令,即可执行Makefile文件的如下代码执行删除文件的指令

    1
    2
    clean:
    rm main main.o cal.o
  • 使用变量-宏简化操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #变量 -宏
    OBJ=main.o cal.o
    BIN=main
    CC=gcc
    RM=rm
    $(BIN):$(OBJ)
    $(CC) $(OBJ) -o main

    %.o:%.c
    $(CC) -c $< -o $@

    #伪目标
    #没有依赖的目标
    #make clean
    clean:
    $(RM) $(BIN) $(OBJ)

15.结构体

查看相关知识
  • 结构体类型的存储区可以包含多个子存储区,不同子存储区的类型可以不同,子存储区也可以是结构体类型的存储区

  • 结构体里不可包含函数

  • 结构体声明语句因为不分配内存可以写在头文件里

  • 结构体变量的声明、定义以及输入输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /**********声明方式1***********/
    struct student{
    char name[32];
    int age;
    };
    struct student s1={"MYAN",25};
    /************声明方式2*********/
    struct student{
    char name[32];
    int age;
    };
    typedef struct student stu_t;
    stu_t s2={"CSC",24};
    /************声明方式3*********/
    typedef struct student{
    char name[32];
    int age;
    }stu_t;
    stu_t s2={"CSC",24};
    /*************输出************/
    printf("%s,%d\n",s1.name,s1.age);
    /*************输入************/
    stu_t s3={0};
    printf("please input name:");
    scanf("%s",s3.name);//由于name成员变量是数组,因此s3.name表示地址
    printf("please input age:");
    scanf("%d",&s3.age);//由于age是整型变量,因此&s3.age表示地址

  • 结构体指针可以记录结构体存储区的地址,建立对应的捆绑关系,以下写法可以通过结构体指针表示结构体的子存储区

    1
    2
    stu_t *ps2=&s2;
    printf("%s,%d\n",ps2->name,ps2->age);
  • 结构体作为函数参数,可以直接使用结构体类型的形式参数从调用函数向被调用函数传递结构体数据,直接使用结构体类型的形式参数会造成时间和空间的浪费,使用结构体指针类型的参数可以避免浪费,使用结构体指针作为形式参数的时候,如果不希望通过结构体指针修改结构体的数据,可以添加const关键字声明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    typedef struct student{
    char name[32];
    int age;
    }stu_t;
    /*************结构体作为形式参数*************/
    void show(stu_t s){
    printf("%s,%d\n",s.name,s.age);
    }
    /***********结构体指针作为形式参数***********/
    void show_p(const stu_t* ps){
    printf("%s:%d\n",ps->name,ps->age);
    // ps->age++;//ok 加const关键字则不可以
    }
  • 结构体作为返回值,可以把结构体类型的变量直接作为返回值使用,这样可以从被调用函数向调用函数传递结构体数据,但是这样会造成时间和空间的浪费,使用结构体存储区的地址作为返回值可以避免这个问题,但是需要注意的是不可以把局部结构体存储区的地址作为返回值使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    typedef struct student{
    char name[32];
    int age;
    }stu_t;
    /*************结构体作为返回值*************/
    stu_t get_stu_info(void){
    stu_t s={"LX",27};
    return s;
    }
    /***********结构体指针作为返回值***********/
    stu_t* get_stu_info_p(void){
    static stu_t s={"HL",18};
    return &s;//直接返回局部变量的地址,不可行,加static可行
    }
  • 结构体数组

    1
    2
    3
    4
    5
    6
    7
    8
    stu_t a[3]={
    {"MYAN",25},
    {"GMN",26},
    {"LX",27}
    };
    for(int i=0;i<sizeof(a)/sizeof(a[0]);i++){
    printf("%s:%d\n",a[i].name,a[i].age);
    }
  • 结构体嵌套结构体:.前面是变量名,->前面是指针名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #include<stdio.h>
    typedef struct birthday{
    int year;
    int month;
    int day;
    }birth_t;
    typedef struct student{
    char name[32];
    int age;
    birth_t birth;
    }stu_t;
    int main(void){
    stu_t s={"JACK",18,{2006,5,18}};
    printf("%s:%d\n",s.name,s.age);
    printf("%d:%d:%d\n",s.birth.year,s.birth.month,s.birth.day);
    stu_t* ps=&s;
    printf("%s:%d\n",ps->name,ps->age);
    /*ps->birth是变量,因此使用ps->birth.month表示月份*/
    printf("%d:%d:%d\n",ps->birth.year,ps->birth.month,ps->birth.day);
    ps->birth.year=2005;
    return 0;
    }
  • 内存对齐和补齐

    • 4字节对齐:结构体中每个成员的起始地址必须是其自身大小的整数倍,超过4字节的按照4字节算
    • 4字节补齐:结构体存储区的大小必须是4的整数倍
    • 8字节补齐:结构体存储区的大小必须是8的整数倍(64位系统)
    • #pragma pack(n)代码以n字节对齐补齐

16.联合体

查看相关知识
  • 联合体成员变量对应的存储区在内存里互相重叠,联合体成员变量的开始地址相同,联合体的大小为占内存最多的成员变量的大小

  • 联合体成员不是相互独立的,且同一时间只能使用一个联合体成员

  • 联合体在解决判断内存存储是大段存储还是小端存储的优势

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    union U
    {
    char c;
    int i;
    }u;//联合体变量创建方法类比结构体
    int main()
    {
    u.i = 1; //0x 00 00 00 01(变量i为int类型,有四个字节,32bit)
    /* 低地址----------------------------------------------------->高地址
    hex:01 00 00 00 bin:0000 0001 0000 0000 0000 0000 0000 0000 小端存储 低位放低地址
    hex:00 00 00 01 bin:0000 0000 0000 0000 0000 0000 0000 0001 大端存储 低位放高地址
    */
    //变量c为char类型,只有一个字节,取前8bit即可
    if (u.c == 1) //即u.c = 0000 0001
    {
    printf("小端");
    }
    else
    {
    printf("大端");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #include <stdio.h>
    //声明联合体数据类型 不占内存空间
    struct person{
    char name[20];
    char sex;
    char job;
    union{
    int class; //班级
    char position[10];//位置 讲师 教授
    }category; //定义了联合体类型变量category
    };
    int main(void){
    struct person persons[2] ={
    {.name="小明", .sex='m', .job='s', .category.class = 2007},
    {.name="老张", .sex='f', .job='t', .category.position="教授"},
    };
    for(int i=0; i<sizeof(persons)/sizeof(persons[0]); i++){
    if(persons[i].job == 's'){
    printf("姓名: %s 性别:%c 班级:%d\n", persons[i].name,
    persons[i].sex, persons[i].category.class);
    }
    else{
    printf("姓名: %s 性别:%c 职称:%s\n", persons[i].name,
    persons[i].sex, persons[i].category.position);
    }
    }
    return 0;
    }

17.枚举

查看相关知识
  • 枚举是一个有限整型常量的列表,每个枚举值都是一个符号常量,默认从0开始,向后依次加1
  • 枚举定义了一些符号,这些符号的本质就是int类型的常量,每个符号和一个常量绑定。这个符号就表示一个自定义的一个识别码,编译器对枚举的认知就是符号常量所绑定的那个int类型的数字

使用枚举其实就是对一些数字进行符号化编码,这样的好处就是编程时可以不用看数字而直接看符号。符号能够见名知意,而数字所代表的含义需要看文档或者注释

枚举能够实现的,宏定义也能实现,但是二者之间有所区别,区别如下:
1、枚举是将多个有关联的符号封装在一个枚举中,而宏定义是完全散的。即枚举是多选一,且只能在这里面选,能防止不符合的数据输入
2、当要定义的常量是一个有限集合时,最适合用枚举
3、不适合用枚举的情况下(比如定义的常量符号之间无关联,或者无限的)使用宏定义

1
2
3
4
5
6
7
8
9
10
11
//先定义类型
enum DAY //类型名称就是enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};

//后声明变量
enum DAY yesterday;
enum DAY today;
enum DAY tomorrow; //变量tomorrow的类型为枚举型enum DAY
enum DAY good_day, bad_day; //变量good_day和bad_day的类型均为枚举型enum DAY

18.二级指针

查看相关知识
  • 二级指针用来记录一级指针的地址

  • 定义int ** ppa=&pa

  • 二级指针变量名称前写**可以表示它所捆绑的普通类型存储区

  • 函数中修改外部一级指针的值,需要传递二级指针才可修改有效

  • 函数名称可以用来表示函数地址,函数指针用来记录函数的地址;函数指针格式返回类型 (*函数指针变量)(形参表)=函数名,通过函数指针调用函数的格式函数指针变量(实参表)

  • 使用函数指针作为形参的函数被称为回调函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    /**************************************/
    #include<stdio.h>
    int main(void){
    int a=100;
    int *pa=&a;
    int **ppa=&pa;
    printf("a=%d,*pa=%d,**ppa=%d\n",a,*pa,**ppa);
    printf("&a=%p,pa=%p,*ppa=%p\n",&a,pa,*ppa);
    printf("ppa=%p,&pa=%p\n",ppa,&pa);
    printf("&ppa=%p\n",&ppa);
    return 0;
    }
    /**************************************/
    #include<stdio.h>
    int main(int argc,char** argv){//字符指针数组可以使用二级指针代替
    for(int i=0;i<argc;i++){
    printf("argv[%d]=%s\n",i,argv[i]);
    }
    return 0;
    }
    /**************************************/
    #include<stdio.h>
    void swap(char** s1,char **s2){
    char* temp="";
    temp=*s1;
    *s1=*s2;
    *s2=temp;
    }
    int main(void){
    char* s1="hello";
    char* s2="world";
    printf("s1=%s,s2=%s\n",s1,s2);
    swap(&s1,&s2);
    printf("s1=%s,s2=%s\n",s1,s2);
    return 0;
    }
    /**************************************/
    #include<stdio.h>
    int add(int x,int y){
    return x+y;
    }
    int sub(int x,int y){
    return x-y;
    }
    int main(void){
    int (*p)(int,int)=add;//函数指针
    int sum=p(100,200);
    printf("%d\n",sum);
    p=sub;
    int ret =p(400,200);
    printf("%d\n",ret);
    return 0;
    }
    /**************************************/
    #include<stdio.h>
    typedef int (*pfunc_t)(int,int);
    int add(int x,int y){
    return x+y;
    }
    int sub(int x,int y){
    return x-y;
    }
    int main(void){
    pfunc_t p=add;//函数指针
    int sum=p(100,200);
    printf("%d\n",sum);
    pfunc_t p1=sub;
    int ret =p1(400,200);
    printf("%d\n",ret);
    return 0;
    }
    /**************************************/
    #include<stdio.h>
    typedef int (*pfunc_t)(int,int);
    int add(int x,int y){
    return x+y;
    }
    int sub(int x,int y){
    return x-y;
    }
    int cal(int a,int b,pfunc_t pfunc){//函数指针作为形参--回调函数
    return pfunc(a,b);
    }
    int main(void){
    printf("%d\n",cal(100,200,add));
    printf("%d\n",cal(100,200,sub));
    return 0;
    }

19.动态分配内存

查看相关知识
  • 动态内存分配:在程序运行时,临时决定需要分配的存储区个数,为了管理动态分配内存需要使用一组标准函数,这些标准函数位于stdlib.h头文件中
  • malloc函数能够动态分配一组连续的存储区,该函需要一个整型参数表示分配的字节数,它的返回值表示分配好的第一个字节的地址,如果内存分配失败就返回NULL,返回值记录在无类型指针存储中,需要强制类型转换成有类型指针才可使用
  • 计算机不会主动回收动态分配的内存,因此防止内存泄漏,需要使用free函数释放动态内存分配,该函数需要地址作为参数,另外释放完动态内存后,需要将指针设置成空指针
  • calloc函数,需要两个参数,第一个参数为分配存储区的个数,第二个参数时分配存储区的大小;calloc与malloc函数的区别为,前者分配的存储区全部初始化为0,而malloc函数分配的存储区为随机数
  • realloc函数需要两个参数,第一个参数为首地址,第二个参数为从首地址上调整的字节个数;若扩充内存大小,原有字节的内容也不会发生改变;若缩小内存大小,则自动舍弃后几个字节的内容
  • 局部变量位于栈区,全局变量和静态变量位于常量区,动态分配存储区位于堆区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<stdio.h>
#include<stdlib.h>
int main(void){
int *p=NULL;
p=(int *)malloc(sizeof(int)*5);
//p=(int *)calloc(5,sizeof(int));
//p=realloc(p,10*sizeof(int));
if(p==NULL){
printf("memeory allocation failed\n");
return -1;
}
printf("memeory allocation success\n");
printf("分配好的存储区的首地址为:%p\n",p);
for(int i=0;i<5;i++){
*(p+i)=100+i;
}
for(int i=0;i<5;i++){
printf("%d ",*(p+i));
}
printf("\n");
free(p);//释放p所捆绑的存储区-堆区p的值仍为动态存储区的地址
p=NULL;// 避免野指针出现
return 0;
}

18.文件操作

查看相关知识
  • 文件操作步骤

    • 打开文件—fopen:该函数需要两个参数,第一个参数文件路径,第二个参数打开方式,返回值为FILE*类型,表示打开的文件;如果文件打开失败,则返回NULL

      • r—只读,文件必须存在,从头开始读
      • w—只写,文件不存在就创建,存在就清空,从头开始写
      • a—追加,文件不存在就创建,存在不清空,从尾开始写
      • r+—读写,文件必须存在,从头开始读写
      • w+—读写,文件不存在就创建,存在就清空,从头开始读写
      • a+—追读,文件不存在就创建,存在不清空,从头开始读,从尾开始写
    • 操作文件—fwrite/fread

      • fwrite写入操作,第一个参数是向文件中写入的数据的存储区的首地址,第二个参数是写入元素占据的字节数,第三个参数是写入的元素个数,第四个参数是写入的文件的文件指针,返回值为实际写入的元素个数

      • fread读操作,第一个参数是从文件中读取的数据要存储到的存储区的首地址,第二个参数是读取的每个元素占据的字节数,第三个参数是要读取的元素个数,第四个参数是要读取文件的文件指针,返回值是实际读取元素的个数

      • 文件读写位置:计算机里为每个打开的文件保留一个整数,这个整数表示下一次文件读写操作的开始位置;这个整数就是文件头到这个位置之间包含的字节个数,这个整数叫做文件的位置指针;每当从文件里获取n个字节或向文件里写入n个字节以后,位置指针都会向后移动n个字节;(1)ftell函数可以用来获取位置指针的数值(返回值为long类型);(2)rewind函数可以把位置指针移动到文件开头;(3)fseek函数可以把位置指针移动到文件里的任何位置,该函数需要三个参数:第一个参数为文件指针,表示要操作的文件;第二个参数为偏移量;第三个参数为基准位置(SEEK_SET:把文件头作为基准位置;SEEK_CUR:把当前位置作为基准位置;SEEK_END:把文件尾作为基准位置)

      • fprintf将数字记录到文件中,fscanf从文本里获得的数据记录到存储区,这两个函数执行效率比较低,不适合处理大数据量的文件

      • 三个标准文件指针:标准输入—stdin—键盘;标准输出—stdout—终端窗口;标准错误—stderr—终端窗口;

        1
        2
        printf("%d\n",a);/**等价于**/fprintf(stdout,"%d\n",a);
        scanf("%d",&a);/**等价于**/fscanf(stdin,"%d",&a);
    • 关闭文件—fclose,该函数的参数为文件指针即fopen的返回值;若文件正常关闭,则返回值为0,否则返回值为非0;同时文件指针需要设置为NULL

  • 案例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    /****************************************/
    int main(void){
    /**************打开文件***********/
    FILE* fp= fopen("a.txt","w+");
    if(fp==NULL){
    printf("can not open the file\n");
    return -1;
    }
    printf("open the file success\n");
    /**************文件读写操作***********/
    int size=0;
    int a[8]={1,2,3,4,5,6,7,8};
    int len=sizeof(a)/sizeof(a[0]);
    size= fwrite(a,sizeof(int),len,fp);
    printf("实际写入%d个数据\n",size);
    rewind(fp);
    int b[8]={};
    size=fread(b,sizeof(int),8,fp);
    printf("实际读取%d个数据\n",size);
    for(int i=0;i<8;i++)
    printf("%d ",b[i]);
    printf("\n");
    /**************文件读写位置***********/
    int c[2]={0};
    fseek(fp,8,SEEK_SET);
    fread(c,sizeof(int),2,fp);//3 4
    printf("%d,%d\n",c[0],c[1]);
    fseek(fp,8,SEEK_CUR);
    fread(c,sizeof(int),2,fp);//7 8
    printf("%d,%d\n",c[0],c[1]);
    fseek(fp,-12,SEEK_END);
    fread(c,sizeof(int),2,fp);//6 7
    printf("%d,%d\n",c[0],c[1]);
    printf("current position:%ld\n",ftell(fp));
    fclose(fp);
    fp=NULL;
    return 0;
    }
    /****************************************/
    #include<stdio.h>
    int main(void){
    FILE* fp=fopen("b.txt","r");
    int a=0;
    char str[20]={};
    double d=0.0;
    fscanf(fp,"%d%s%lg",&a,str,&d);
    printf("a=%d,str=%s,d=%lg\n",a,str,d);
    fclose(fp);
    fp=NULL;
    FILE* fpw=fopen("c.txt","w+");
    fprintf(fpw,"%d,%s,%lg\n",a,str,d);
    fclose(fpw);
    fpw=NULL;
    return 0;
    }
    /****************************************/

数据结构与算法

查看相关知识

一、数据结构

查看相关知识

1.基本概念

查看相关知识
  • 数据结构分为逻辑结构与物理结构
    • 逻辑结构:数据与数据之间的相互关系;集合结构、线性结构、树形结构(一对多)、图形结构(多对多)
    • 物理结构:数据在计算机中的存储形式;顺序存储结构、链式存储结构

2.栈

查看相关知识

栈特点:先进后出,后进先出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//stack.h:栈的各种声明
#ifndef __STACK_H
#define __STACK_H
//包含公共的头文件
#include <stdio.h>
#include <stdlib.h>
//声明描述栈的属性的结构体
typedef struct stack {
int *arr; //首地址
int cap; //容量
int top; //记录栈顶位置
}stack_t;
//stack_t stack;//是否分配了内存
//声明栈的操作函数
extern void stack_init(stack_t *stack, int cap); //初始化栈
extern void stack_deinit(stack_t *stack);//释放栈内存
extern int stack_full(stack_t *stack);//判断栈是否满
extern int stack_empty(stack_t *stack);//判断栈是否空
extern void stack_push(stack_t *stack, int data); //压栈
extern int stack_pop(stack_t *stack); //出栈
extern int stack_size(stack_t *stack);//获取栈中有效数据个数
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//stack.c:栈的各种定义
#include "stack.h"
//定义栈的初始化函数
void stack_init(stack_t *stack, int cap) {
stack->arr = malloc(sizeof(int)*cap);//给栈分配内存
stack->cap = cap; //初始化容量
stack->top = 0; //空栈,一开始没有数据
}
//定义栈内存释放函数
void stack_deinit(stack_t *stack) {
free(stack->arr);//释放栈内存
stack->cap = 0;
stack->top = 0;
}
//定义判断栈空和满的函数
int stack_full(stack_t *stack) {
return stack->top == stack->cap; //满返回1,否则返回0
}
int stack_empty(stack_t *stack) {
return stack->top == 0;//空返回1,否则返回0
}
//定义压栈函数
void stack_push(stack_t *stack, int data) {
stack->arr[stack->top] = data;
stack->top++;
}
//定义出栈函数
int stack_pop(stack_t *stack) {
return stack->arr[--stack->top];
}
//定义获取栈有效数据个数函数
int stack_size(stack_t *stack) {
return stack->top;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//main.c:测试
#include "stack.h" //声明
int main(void) {
//定义栈变量
stack_t stack;//arr, cap, top
//初始化
//通过stack的地址来访问stack的arr,cap,top
//stack_t* stack = &stack;
stack_init(&stack, 10);//指定容量是10
int data = 1;
while(!stack_full(&stack))//循环压栈
stack_push(&stack, data++);
printf("有效个数是:%d\n", stack_size(&stack));
while(!stack_empty(&stack))//循环出栈
printf("%d ", stack_pop(&stack));
printf("\n");
printf("有效个数是:%d\n", stack_size(&stack));
stack_deinit(&stack); //释放内存
return 0;
}

3.队列

查看相关知识

队列特点:先进先出,后进后出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//queue.h:队列的声明
#ifndef __QUEUE_H
#define __QUEUE_H
//包含功能的头文件
#include <stdio.h>
#include <stdlib.h>
//声明描述队列属性的结构体
typedef struct queue {
int *arr; //首地址
int cap;//容量
int size; //有效数据个数
int rear; //用于入队
int front; //用于出队
}queue_t;
extern void queue_init(queue_t *queue, int cap);//初始化
extern void queue_deinit(queue_t *queue);//释放内存
extern int queue_full(queue_t *queue);//判断满
extern int queue_empty(queue_t *queue);//判断空
extern void queue_push(queue_t *queue, int data);//入队
extern int queue_pop(queue_t *queue);//出队
extern int queue_size(queue_t *queue);//获取有效数据个数
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//queue.c:队列定义
#include "queue.h"
//定义初始化队列函数
void queue_init(queue_t *queue, int cap) {
queue->arr = malloc(sizeof(int)*cap);//分配内存
queue->cap = cap;
queue->size = 0;
queue->front = 0;
queue->rear = 0;
}
//定义释放内存函数
void queue_deinit(queue_t *queue) {
free(queue->arr);
queue->cap = 0;
queue->size = 0;
queue->front = 0;
queue->rear = 0;
}
//定义判断满的函数
int queue_full(queue_t *queue) {
return queue->size >= queue->cap; //满返回1,否则返回0
}
//定义判断空的函数
int queue_empty(queue_t *queue) {
return !queue->size; //空返回1,否则返回0
}
//定义判断入队和出队函数
void queue_push(queue_t *queue, int data) {
if(queue->rear >= queue->cap)
queue->rear = 0; //构造循环队列
queue->arr[queue->rear++] = data;
queue->size++; //更新计数
}
int queue_pop(queue_t *queue) {
if(queue->front >= queue->cap)
queue->front = 0;//构造循环队列
queue->size--; //更新计数
return queue->arr[queue->front++];
}
//定义获取有效数据个数函数
int queue_size(queue_t *queue) {
return queue->size;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//main.c:测试
#include "queue.h"
int main(void) {
queue_t queue; //定义队列
queue_init(&queue, 4);//初始化队列,容量为4个数据
for(int i = 10; i <= 40; i += 10)
if(!queue_full(&queue))
queue_push(&queue, i); //入队:10 20 30 40
printf("有效数据个数:%d\n", queue_size(&queue));
for(int i = 0; i < 2; i++)
if(!queue_empty(&queue))
printf("%d ", queue_pop(&queue)); //出10 20
printf("\n");
printf("有效数据个数:%d\n", queue_size(&queue));
for(int i = 50; i <= 60; i += 10)
if(!queue_full(&queue))
queue_push(&queue, i); //入队:50 60 30 40
printf("有效数据个数:%d\n", queue_size(&queue));
while(!queue_empty(&queue)) //整体出队:30 40 50 60
printf("%d ", queue_pop(&queue));
printf("\n");
queue_deinit(&queue); //释放内存
return 0;
}

4.单链表

查看相关知识
  • 为了便于对链表的操作,会在链表的第一个节点前再附设一个节点称为头节点;最后一个节点再辐射一个节点,称为尾节点;头尾节点不用来存储数据,除头尾节点外的其他节点称为有效节点
  • pnodepfirst指针变化范围head节点至最后一个有效节点结束;pmid指针的变化范围从第一个有效节点开始到尾节点结束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//list.h:单链表的声明
#ifndef __LIST_H
#define __LIST_H
//包含公共的头文件
#include <stdio.h>
#include <stdlib.h>
//声明描述节点属性的结构体
typedef struct node {
int data; //节点数据
struct node *next;//保存下一个节点的首地址
}node_t;
//声明描述整个单链表的结构体
typedef struct list {
struct node *head; //保存头结点的首地址
struct node *tail; //保存尾节点的首地址
}list_t;
extern void list_init(list_t *list);//初始化
extern void list_deinit(list_t *list);//释放内存
extern void list_travel(list_t *list);//遍历
extern void list_add(list_t *list, int data);//顺序插
extern void list_add_first(list_t *list, int data);//前插
extern void list_add_last(list_t *list, int data);//后插
extern void list_del(list_t *list, int data);//删除data所在的节点
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//list.c:单链表的定义
#include "list.h"
//定义分配新节点内存函数
static node_t *create_node(int data) {
node_t *pnew = malloc(sizeof(node_t)); //分配节点内存
pnew->data = data; //初始化节点数据
pnew->next = NULL;
return pnew; //返回新节点的首地址
}
//定义单链表的初始化函数
void list_init(list_t *list) {
//1.给头节点分配内存
list->head = create_node(0);
//2.给尾节点分配内存
list->tail = create_node(0);
//3.头指向尾
list->head->next = list->tail;
list->tail->next = NULL;
}
//定义单链表的遍历函数
void list_travel(list_t *list) {
for(node_t *pnode = list->head; pnode != list->tail; pnode = pnode->next) {
//1.定义三个游标
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
//2.判断pmid是否是有效节点
if(pmid != list->tail)
printf("%d ", pmid->data);
}
printf("\n");
}
//定义顺序新节点插函数
void list_add(list_t *list, int data) {
//1.创建新节点
node_t *pnew = create_node(data);
//2.遍历找到要插入的位置,让pmid指向后一个节点,pfirst指向前一个节点
//那么pnew新节点就插入到pfirst和pmid中间即可
for(node_t *pnode = list->head; pnode != list->tail; pnode = pnode->next) {
//3.定义三个游标
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(pmid->data >= pnew->data /*新节点小于后一个节点插入*/
|| pmid == list->tail/*新节点比前面的节点数据都大,放在最后一个即可*/)
{
pfirst->next = pnew;
pnew->next = pmid;
break;
}
}
}
//定义前插函数和后插函数
void list_add_first(list_t *list, int data) {
//1.创建新节点
node_t *pnew = create_node(data);
//2.插入新节点pnew到头结点和原先的第一个节点中间
node_t *ptmp = list->head->next; //临时备份原先的第一个节点
list->head->next = pnew;
pnew->next = ptmp;
}
void list_add_last(list_t *list, int data) {
//1.创建新节点
node_t *pnew = create_node(data);
//2.遍历找到最后一个节点让pfirst指向最后一个节点,pmid指向尾节点
for(node_t *pnode = list->head; pnode != list->tail; pnode = pnode->next) {
//3.定义三个游标
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(pmid == list->tail) { //如果pmid指向了尾节点,那么pfirst肯定指向原先的最后一个节点
pfirst->next = pnew;
pnew->next = pmid;
break;
}
}
}
//定义删除指定数字所在的节点函数
void list_del(list_t *list, int data) {
//1.遍历找到要删除的节点,然后让pmid指向要删除的节点,那么pfirst指向前一个节点
//plast指向后一个节点
for(node_t *pnode = list->head; pnode != list->tail; pnode = pnode->next) {
//2.定义三个游标
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(pmid->data == data && pmid != list->tail/*不能删除尾节点*/) {
pfirst->next = plast;//让前一个节点pfirst指向后一个节点plast即可
free(pmid); //释放内存
break;
}
}
}
//定义释放链表所有节点的函数
void list_deinit(list_t *list) {
node_t *pnode = list->head;
while(pnode) {
node_t *ptmp = pnode->next; //临时备份下一个节点
free(pnode); //释放pnode指向的节点内存
pnode = ptmp; //pnode再指向要释放的下一个节点
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//main.c:单链表测试
#include "list.h"
int main(void) {
//1.创建单链表
list_t list;
//2.初始化单链表
list_init(&list);
//3.各种插入和遍历
list_add(&list, 10);
list_add(&list, 20);
list_add(&list, 30);
list_travel(&list);
list_add_first(&list, 5);
list_add_first(&list, 3);
list_travel(&list);
list_add_last(&list, 40);
list_add_last(&list, 50);
list_travel(&list);
//4.删除节点
list_del(&list, 20);
list_travel(&list);
//5.释放整个链表
list_deinit(&list);
return 0;
}

5.双链表

查看相关知识
  • 每个节点设置一个指向前面节点的指针,一个指向后面节点的指针,即双链表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//list.h:双链表的各种声明
#ifndef __LIST_H
#define __LIST_H
//包含公共的头文件
#include <stdio.h>
#include <stdlib.h>
//声明描述节点信息的结构体
typedef struct node {
int data; //数据
struct node *next;//保存下一个节点的首地址
struct node *prev; //保存上一个节点的首地址
}node_t;
//声明描述整个链表的结构体
typedef struct list {
struct node head;//头节点
struct node tail;//尾节点
}list_t;
extern void list_init(list_t *list);//初始化函数
extern void list_deinit(list_t *list);//释放内存函数
extern int list_empty(list_t *list);//判断空
extern int list_size(list_t *list);//获取有效节点个数函数
extern void list_add(list_t *list, int data);//顺序插
extern void list_add_first(list_t *list, int data);//前插
extern void list_add_last(list_t *list, int data);//后插
extern void list_del(list_t *list, int data);//删除指定节点
extern void list_del_first(list_t *list);//只删除第一个节点
extern void list_del_last(list_t *list);//只删除最后一个节点
extern int list_get(list_t *list, int index);//根据节点编号获取节点数据
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//list.c:双链表的各种定义
#include "list.h"
//定义初始化函数
void list_init(list_t *list) {
//1.头指向尾
list->head.next = &list->tail;
//2.尾指向头
list->tail.prev = &list->head;
list->head.data = 0;
list->tail.data = 0;
list->head.prev = NULL;
list->tail.next = NULL;
}
//定义判断链表是否为空
int list_empty(list_t *list) {
return list->head.next == &list->tail;//空返回1,否则返回0
}
//定义获取节点个数的函数
int list_size(list_t *list) {
int count = 0; //记录节点个数
for(node_t *pnode = &list->head; pnode != &list->tail; pnode=pnode->next) {
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(pmid != &list->tail) //pmid不等于尾节点,那么pmid指向的节点必然是有效节点
count++; //更新计数
}
return count; //返回节点个数
}
//定义创建新节点函数
static node_t *create_node(int data) {
node_t *pnew = malloc(sizeof(node_t)); //分配内存
pnew->data = data;
pnew->next = NULL;
pnew->prev = NULL;
return pnew; //返回新节点的首地址
}
//定义插入新节点函数,将新节点pnew插入到pfirst和pmid中间
static void insert_node(node_t *pfirst, node_t *pmid, node_t *pnew) {
pfirst->next = pnew;
pnew->prev = pfirst;
pnew->next = pmid;
pmid->prev = pnew;
}
//定义顺序插函数
void list_add(list_t *list, int data) {
//1.给data创建所在的新节点
node_t *pnew = create_node(data);
//2.遍历找到要插入的位置,将新节点插入到pfirst和pmid中间
for(node_t *pnode = &list->head; pnode != &list->tail; pnode=pnode->next) {
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(pmid->data > pnew->data || pmid == &list->tail/*新节点最大,放到最后一个*/) {
insert_node(pfirst, pmid, pnew);
break;
}
}
}
//定义前插函数
void list_add_first(list_t *list, int data) {
//1.创建新节点
node_t *pnew = create_node(data);
//2.造游标
node_t *pfirst = &list->head; //pfirst指向头节点
node_t *pmid = pfirst->next; //pmid指向原先的第一个节点
node_t *plast = pmid->next; //plast指向第二个节点或者尾节点
//3.将新节点pnew插入到pfirst和pmid中间,也就是插入到头节点和原先第一个节点中间
insert_node(pfirst, pmid, pnew);
}
//定义后插函数
void list_add_last(list_t *list, int data) {
//1.创建新节点
node_t *pnew = create_node(data);
//2.造游标
node_t *pfirst = list->tail.prev; //pfirst指向原先最后一个节点
node_t *pmid = pfirst->next; //pmid指向尾节点
node_t *plast = pmid->next; //plast指向NULL
//3.将新节点pnew插入到pfirst和pmid中间,也就是插入到原先最后一个节点和尾节点中间
insert_node(pfirst, pmid, pnew);
}
//定义删除pmid指向的节点函数
static void del_node(node_t *pfirst, node_t *pmid, node_t *plast) {
pfirst->next = plast;
plast->prev = pfirst;
free(pmid); //释放节点内存
}
//定义删除指定的节点
void list_del(list_t *list, int data) {
//1.遍历找到要删除的节点,然后让pmid指向要删除的节点
for(node_t *pnode = &list->head; pnode != &list->tail; pnode=pnode->next) {
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(pmid->data == data && pmid != &list->tail/*尾节点不能删*/) {
del_node(pfirst, pmid, plast);//删除pmid指向的节点,连接pfirst和plast
break;
}
}
}
//定义只删除第一个节点
void list_del_first(list_t *list) {
//1.判断链表是否为空
if(list_empty(list)) {
printf("链表空了.\n");
return;
}
//2.造游标
node_t *pfirst = &list->head; //指向头
node_t *pmid = pfirst->next; //指向要删除的第一个节点
node_t *plast = pmid->next; //指向后一个节点或者尾节点
//3.删除
del_node(pfirst, pmid, plast);
}
//定义只删除最后一个节点
void list_del_last(list_t *list) {
//1.判断链表是否为空
if(list_empty(list)) {
printf("链表空了.\n");
return;
}
//2.造游标
node_t *plast = &list->tail; //plast指向尾节点
node_t *pmid = plast->prev; //pmid指向要删除的最后一个节点
node_t *pfirst = pmid->prev; //pfirst指向前一个节点或者head
//3.删除
del_node(pfirst, pmid, plast);
}
//定义根据节点编号获取节点数据
int list_get(list_t *list, int index) {
int count = 0; //记录循环的次数
for(node_t *pnode = &list->head; pnode != &list->tail; pnode = pnode->next) {
node_t *pfirst = pnode;
node_t *pmid = pfirst->next;
node_t *plast = pmid->next;
if(index == count && pmid != &list->tail)
return pmid->data;
count++;
}
}
//定义清空双链表所有节点的函数
void list_deinit(list_t *list) {
while(list->head.next != &list->tail) { //说明链表中还有节点
node_t *pfirst = &list->head; //pfirst永远指向头结点
node_t *pmid = pfirst->next; //pmid指向要清除的第一个节点
node_t *plast = pmid->next; //plast指向第二个节点
del_node(pfirst, pmid, plast);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//main.c:双链表测试
#include "list.h"
int main(void) {
list_t list;//定义
list_init(&list);//初始化
//各种插入
list_add_first(&list, 50);
list_add_first(&list, 20);
list_add_last(&list, 70);//20 50 70
list_add_last(&list, 100);//20 50 70 100
list_add(&list, 80);
list_add(&list, 30);
list_add(&list, 40);
list_add(&list, 60);
list_add(&list, 90);
list_add(&list, 10);
int size = list_size(&list);//获取节点个数
for(int i = 0; i < size; i++)
printf("%d ", list_get(&list, i));//遍历
printf("\n");
list_del_first(&list);
list_del_last(&list);
list_del(&list, 50);
size = list_size(&list);//获取节点个数
for(int i = 0; i < size; i++)
printf("%d ", list_get(&list, i));//遍历
printf("\n");
list_deinit(&list); //清除链表
return 0;
}

6.二叉树

查看相关知识
  • 树中的每个元素称为节点,每个节点最多含有两个子树的树称为二叉树
  • 有序二叉树:左子树元素的值小于根节点,右子树的元素值大于根节点
  • 遍历:前序遍历—根左右;中序遍历—左根右;后序遍历—左右根

  • 有序二叉树的删除步骤:找所要删除的二叉树节点,找新的父节点(如果要删除的节点存在左子树,则将其左子树插入到右子树上,如果不存在左子树,则无需操作),提一级(将要删除的节点的父节点指向删除节点的右子树上)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//tree.h:二叉树各种声明
#ifndef __TREE_H
#define __TREE_H
//包含公共的头文件
#include <stdio.h>
#include <stdlib.h>
//声明描述节点属性的结构体
typedef struct node {
int data; //数据
struct node *left; //保存左节点的首地址
struct node *right; //保存右节点首地址
}node_t;
//声明描述整个二叉树的结构体
typedef struct tree {
struct node *root; //保存根节点的首地址
int cnt; //保存节点个数
}tree_t;
extern void tree_travel(tree_t *tree); //遍历
extern void tree_clear(tree_t *tree); //清空所有节点
extern void tree_insert(tree_t *tree, int data);//插入新节点
extern void tree_del(tree_t *tree, int data);//删除指定节点
extern void tree_modify(tree_t *tree, int old_data, int new_data);//修改节点值
#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//tree.c:有序二叉树各种定义
#include "tree.h"

//定义遍历的递归函数
static void travel(node_t *proot) {

//中序遍历
if(proot != NULL) {
travel(proot->left); //打印左节点
printf("%d ", proot->data); //打印节点数据
travel(proot->right); //打印右节点
return;
}
/*
//先序遍历
if(proot != NULL) {
printf("%d ", proot->data); //打印节点数据
travel(proot->left); //打印左节点
travel(proot->right); //打印右节点
return;
}
//后序遍历
if(proot != NULL) {
travel(proot->left); //打印左节点
travel(proot->right); //打印右节点
printf("%d ", proot->data); //打印节点数据
return;
}
*/
return;
};
//定义有序二叉树遍历函数
void tree_travel(tree_t *tree) {
//调用递归函数从根节点开始遍历
travel(tree->root);
printf("\n");
}

//定义清空节点的递归函数
void clear(node_t **pproot) {
if(*pproot != NULL) {
clear(&(*pproot)->left); //清空左子树
clear(&(*pproot)->right); //清空左子树
free(*pproot); //释放节点内存
*pproot = NULL; //修改指向释放节点的父节点指向NULL,否则就是野指针
return;
}
return;
}
//定义清空二叉树所有节点函数
void tree_clear(tree_t *tree) {
//调用递归函数从根节点开始清空
clear(&tree->root);
}
//定义分配新节点的内存函数
static node_t *create_node(int data) {
node_t *pnew = malloc(sizeof(node_t));
pnew->data = data;
pnew->left = NULL;
pnew->right = NULL;
return pnew;
}

//定义插入新节点的递归函数
static void insert(node_t **pproot, node_t *pnew) {
if(*pproot == NULL) {
*pproot = pnew; //插入新节点
return;
}
if((*pproot)->data > pnew->data) {
insert(&(*pproot)->left, pnew); //将新节点插入插入到左子树
return;
} else {
insert(&(*pproot)->right, pnew); //将新节点插入插入到右子树
return;
}
return;
}
//定义插入新节点的函数
void tree_insert(tree_t *tree, int data) {
//1.创建新节点
node_t *pnew = create_node(data);
//2.调用递归函数插入新节点
insert(&tree->root, pnew);
//3.更新计数
tree->cnt++;
}

//定义查找节点的递归函数
static node_t **find(node_t **pproot, int data) {
//1.停止查找
if(*pproot == NULL)
return pproot; //没有找到
//2.比较
if((*pproot)->data == data)
return pproot; //找到了
else if(data < (*pproot)->data)
return find(&(*pproot)->left, data); //左边找
else
return find(&(*pproot)->right, data); //右边找
}
//定义查找节点函数
static node_t **find_node(tree_t *tree, int data) {
//调用递归函数从根节点开始查找要删除的节点
return find(&(tree)->root, data);
}
//定义删除指定的节点函数
void tree_del(tree_t *tree, int data) {
//1.找节点
node_t **ppnode = find_node(tree, data);
if(*ppnode == NULL) {
printf("没有找到节点.\n");
return;
}
//2.找新爹:如果要删除的节点的左子树不为空,将左子树插入到右子树上
if((*ppnode)->left != NULL)
insert(&(*ppnode)->right, (*ppnode)->left);
//3.提一级:将要删除的节点的父节点的左子树指向要删除的节点的右子树上
//(*ppnode):就是50的L
//(*ppnode)->right:删除的节点的右子树
node_t *ptmp = *ppnode; //临时备份要删除的节点,将来用于free,ptmp指向20
(*ppnode) = (*ppnode)->right; //50的L指向40
free(ptmp); //释放20
tree->cnt--; //更新计数
}
//定义修改节点值的函数
void tree_modify(tree_t *tree, int old_data, int new_data) {
//1.先删除节点
tree_del(tree, old_data);
//2.插入新节点
tree_insert(tree, new_data);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//main.c:有序二叉树测试
#include "tree.h"
int main(void) {
tree_t tree;
tree.root = NULL;
tree.cnt = 0;
tree_insert(&tree, 50);
tree_insert(&tree, 70);
tree_insert(&tree, 60);
tree_insert(&tree, 20);
tree_insert(&tree, 40);
tree_insert(&tree, 30);
tree_insert(&tree, 10);
tree_insert(&tree, 90);
tree_insert(&tree, 80);
tree_travel(&tree);
tree_del(&tree, 40);
tree_del(&tree, 40);
tree_travel(&tree);
tree_modify(&tree, 10, 520);
tree_travel(&tree);
tree_clear(&tree);
tree_travel(&tree);
return 0;
}

二、算法

查看相关知识

1.排序算法

查看相关知识
  • 冒牌排序

    重复地走过要排序的数列,依次比较俩个元素,若顺序错误,则交换位置,直至无需要交换为止,则排序完成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #include<stdio.h>
    //冒泡排序
    void bubble_sort(int arr[],int size){
    for(int i=0;i<size-1;i++){
    int flag=1;
    for(int j=0;j<size-i-1;j++){
    if(arr[j+1]<arr[j]){
    int temp=0;
    temp=arr[j];
    arr[j]=arr[j+1];
    arr[j+1]=temp;
    flag=0;
    }
    }
    if(flag)
    break;
    }
    }

  • 插入排序

    从无序数列中取出数据单独放到有序的数列中,从有序数列的最后一个元素起依次进行扫描比较,因此第一个元素本身有序,无需插入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #include<stdio.h>
    void insert_sort(int data[],int num)
    {
    for(int i=1;i<num;i++)
    {
    if(data[i]<data[i-1])
    {
    int j=i-1;
    while(data[j+1]<data[j])
    {
    int insert_data=data[i];
    data[i]=data[j];
    data[j]=insert_data;
    j--;
    }
    }
    }
    }
    void main()
    {
    int data[6]={10,5,30,15,20,60};
    insert_sort(data,6);
    for(int i=0;i<6;i++)
    printf("%d\t",data[i]);
    }

  • 选择排序

    从无序的数列中选择最大的放置有序的内存的第一个,紧接着放置第二大的数,依次类推

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #include<stdio.h>
    void select_sort(int data[],int num)
    {
    for(int i=0;i<num-1;i++)
    {
    for(int j=i+1;j<num;j++)
    {
    if(data[i]<data[j])
    {
    int temp=data[i];
    data[i]=data[j];
    data[j]=temp;
    }
    }
    }
    }

    void main()
    {
    int data[6]={20,15,5,32,1,66};
    select_sort(data,6);
    for(int i=0;i<6;i++)
    {
    printf("%d\t",data[i]);
    }
    }
  • 快速排序

    快速排序采用的是分治思想,即在一个无序的序列中选取一个任意的基准元素pivot,利用pivot将待排序的序列分成两部分,前面部分元素均小于或等于基准元素,后面部分均大于或等于基准元素,然后采用递归的方法分别对前后两部分重复上述操作,直到将无序序列排列成有序序列

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    void quick_sort(int data[], int left, int right) {
    int p = (left+right)/2; //定义基准值对应的下标,可以随意指定
    int i = left; //left=0,左边界
    int j = right; //right=size-1,右边界
    int pivot = data[p]; //暂存基准值
    while(i < j) { //进行一次分组,i=j停止分组
    //i不做i++运算的两种情况:i=p重合或者i对应的值大于基准值
    for(; !(i>=p || pivot < data[i]); i++);
    //此种情况就是i对应的值大于基准值而退出了上面的for循环,而不是i=p重合的情况
    if(i < p) {
    data[p] = data[i]; //将i对应的值挪到p的位置
    p = i; //将p调整到i的位置
    }
    //j不做j--运算的两种情况:j=p重合或者j对应的值小于基准值
    for(; !(j<=p || pivot > data[j]); j--);
    //此种情况就是j对应的值小于基准值而退出了上面的for循环,而不是j=p重合的情况
    if(j > p) {
    data[p] = data[j]; //将j对应的值挪到p的位置
    p = j;//调整p到j的位置
    }
    }
    data[p] = pivot; //一次分组结束将基准值挪到p的位置
    if(p - left > 1)//基准值左边的数据个数大于等于2个继续分组直到剩余1个或者没有无需分组
    quick_sort(data, left, p-1);//对左边继续分区,左边界还是0,右边界是p-1,不包含p
    if(right - p > 1)//基准值右边的数据个数大于等于2个继续分组直到剩余1个或者没有无需分组
    quick_sort(data, p+1, right);//对右边继续分组,右边界还是right,左边界是p+1,不包含p
    }

2.查找算法

查看相关知识
  • 线性查找

    1
    2
    3
    4
    5
    6
    7
    //定义线性查找函数
    int line_find(int data[], int size, int key) {
    for(int i = 0; i < size; i++)
    if(data[i] == key)
    return i; //找到并且返回对应的下标
    return -1; //没有找到
    }
  • 二分查找

    在一个有序表中,每次取中间记录数同目标值进行比较。若该记录数等于目标值,则查找成功;若大于目标值,则说明目标值在该查找表的左半区,此时我们往左半区进行查找;若小于目标值,则说明目标值在该查找表的右半区此时我们往右半区进行查找。如此往复,直至查找成功;若无和目标值相等的记录数,则查找失败

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //定义递归二分查找函数
    static int recu_find(int data[], int left, int right, int key) {
    if(left <= right) {
    int mid = (left+right)/2; //折半
    if(key < data[mid]) //左边找
    return recu_find(data, left, mid-1, key);
    else if(key > data[mid]) //右边找
    return recu_find(data, mid+1, right, key);
    else
    return mid; //找到了
    }
    return -1; //没有找到
    }
    //定义二分查找函数
    int half_find(int data[], int size, int key) {
    //递归来实现
    return recu_find(data, 0, size-1, key);
    }