1. 数据结构的概述

1.1 数据结构的定义

我们如何把现实中大量而复杂的问题以特定的数据类型(个体)和特定的存储结构(个体间的关系)保存到计算机的内存中,以及在此基础上为了实现某个功能而执行的相应的操作,这个相应的操作也叫作算法。

  • 数据结构 = 个体 + 个体间的关系
  • 算法 = 对存储数据的操作
    • 解题的方法和步骤
  • 衡量算法的标准

    • 时间复杂度:程序大概要执行的次数,而非执行的时间
    • 空间复杂度:算法执行过程中大概所占用的最大内存
    • 难易程度:即算法转换为程序的可行性
    • 健壮性:也就是容错,处理异常情况的能力

      1.2 数据结构的地位:

    • 数据结构是软件中最核心的课程

    • 程序 = 数据的存储 + 数据的操作 + 可以被计算机执行的语言

1.3 指针

  • 指针的重要性:指针是c的灵魂

    1.3.1 指针的定义:

  • 地址:

    • 地址就是内存单元的编号
    • 从0开始的非负整数
    • 返回0-FFFFFFFF
  • 指针:

    • 指针就是地址,地址就是指针,指针的本质就是一个操作受限的非负整数值,也就是存储单元的地址编号值
    • 指针变量:指针变量就是用来存放内存单元地址的变量

      1.3.2 基本类型的指针

  • & 取地址

  • *取值 ```python

    include

int main(void){ // p是个指针变量的名字,int表示该p变量只能存储int类型的变量地址,p会有一个默认随机地址,此时p则代表了一个随机地址对应的数据值 int *p;
int i = 10; int j;

  1. // &i为取i的地址,将i的地址赋给了p,p就指向了i,*p就是取i的值,修改i的值不会影响p的值,修改p的值不会影响i的值,但是会影响p的指向
  2. p = &i;
  3. j = *p; // *p就代表i的值,等价于j=i
  4. printf("i=%d,j=%d,*p=%d\n",i,j,*p);
  5. return 0 ;

}

  1. <a name="JkqF9"></a>
  2. #### 1.3.2.1 如何通过被调函数修改主调函数中普通变量的值
  3. - 实参为相关变量的地址
  4. - 形参为以改变了的类型为类型的指针变量
  5. - 在背调函数中通过 *形参变量名 的方式就可以修改主调函数变量的值
  6. ```python
  7. # include <stdio.h>
  8. // 如何通过被调函数修改主调函数中普通变量的值
  9. // - 实参为相关变量的地址
  10. // - 形参为以改变了的类型为类型的指针变量
  11. // - 在背调函数中通过 *形参变量名 的方式就可以修改主调函数变量的值
  12. void f(int *p){ // 定义了一个指针类型的形参p,p可以接收int类型变量的地址
  13. // 将100赋值给指针变量p所指向的地址的变量,也就是将传入函数f的地址所对应的变量的值修改为100
  14. *p = 100;
  15. }
  16. int main(void){
  17. int i = 9;
  18. f(&i);
  19. printf("i=%d",i);
  20. return 0;
  21. }

1.3.3 指针和一维数组

1.3.3.1 数组名:

一维数组名是一个指针常量,它存放的是一维数组的第一个元素的地址,他的值不能被改变
一维数组名指向的是数组的第一个元素

1.3.3.2 数组下标和指针的关系

a[i] == *(a+i)
假设指针变量的名字为p,则p+i的值是第i+1个元素的地址
指针变量的运算
指针变量不能进行加,乘除运算
如果两个指针变量同属于一个数组则可以相减
指针变量可以加上或者减去一个数字

  1. # include <stdio.h>
  2. int main(void){
  3. int a[5] = {1,2,3,4,5};
  4. printf("a[0]=%d,*a=%d\n",a[0],*a);
  5. printf("%p\n",a);
  6. printf("%p\n",a+1);
  7. return 0;
  8. }

1.3.3.3 如何通过被调函数修改主调函数中的一维数组

  • 两个参数存放数组首元素的指针变量
  • 存放数组元素长度的整型变量 ```python

    include

// 如何通过背调函数修改主调函数中的一位数组 // 两个参数 存放数组首元素的指针变量 // 存放数组元素长度的整型变量 void showArray(int p, int len){ // p[i]就等于a[i] int i =0; for(i=0;i<len;i++) printf(“a[%d]=%d\n”,i,(p+i)); // p[i]等价于*(p+i) }

int main(){ int a[5] = {1,2,3,4,5}; showArray(a,5);

  1. return 0;

}

  1. <a name="EfoYd"></a>
  2. ## 1.4 结构体
  3. <a name="ebSWv"></a>
  4. ### 1.4.1 为什么会出现结构体
  5. 为了表示一些复杂的数据,而普通的基本类型无法满足
  6. <a name="8cSco"></a>
  7. ### 1.4.2 什么是结构体
  8. 结构体是用户根据实际需求自己设计的复合数据类型
  9. <a name="mRXhH"></a>
  10. ### 1.4.3 如何使用结构体
  11. ```c
  12. // struct Student st = {1000,"张三",20};
  13. struct Student st;
  14. st.sid = 1001;
  15. strcpy(st.name,"李四");
  16. st.age = 21;
  17. struct Student * pst = &st;

1.4.3.1 方式一:st.sid

1.4.3.2 方式二:pst -> sid :pst所指向的结构体变量中的sid这个成员

  • 注意事项:
    • 结构体变量不能加减乘除,但是可以相互赋值
    • 普通结构体变量和结构体指针变量作为函数传参的问题
  • code1 结构体变量的定义和基本使用方法 ```c

    include

    include

struct Student{ // 结构体名称首字母大写与java中的类一样 // 结构体中可以有多个成员 int sid; char name[50]; int age; }; // 结构体反括号后面的分号不能省略,必须写上

int main(void){ // 定义struct Student类型的结构体变量 // struct Student st = {1000,”张三”,20}; 直接赋值 // 先定义,后赋值 struct Student st; // 在定义结构体变量st的时候操作系统就为st分配了内存,内存大小为58个字节 st.sid = 1001; strcpy(st.name,”李四”); // st.name = “李四”; C语言中不能直接这样赋值 需要使用字符串复制函数strcpy(变量名,变量值)进行赋值 st.age = 21; printf(“%d %s %d”,st.sid,st.name,st.age); return 0; }

  1. - code2:用指针访问结构体变量
  2. ```c
  3. # include <stdio.h>
  4. struct Student{ // 结构体名称首字母大写与java中的类一样
  5. // 结构体中可以有多个成员
  6. int sid;
  7. char name[50];
  8. int age;
  9. }; // 结构体反括号后面的分号不能省略,必须写上
  10. int main(void){
  11. // 定义struct Student类型的结构体变量
  12. struct Student st = {1000,"张三",20}; // 直接赋值
  13. // 结构体名.成员名的方式访问
  14. // struct Student st;
  15. // st.sid = 1001;
  16. // strcpy(st.name,"李四"); // st.name = "李四"; C语言中不能直接这样赋值 需要使用字符串复制函数strcpy(变量名,变量值)进行赋值
  17. // st.age = 21;
  18. // 指针访问结构体
  19. struct Student * pst; // pst->sid 等价于 (*pst).sid 而(*pst).sid等价与st.sid 所以pst->sid等价于st.sid
  20. pst = &st;
  21. pst -> sid = 1002;
  22. printf("%d",*pst);
  23. return 0;
  24. }
  • code3:普通结构体变量和结构体变量类型的指针作为函数参数传递的差异 ```c

    include

    include

struct Student{ // 结构体名称首字母大写与java中的类一样 // 结构体中可以有多个成员 int sid; char name[50]; int age; }; // 结构体反括号后面的分号不能省略,必须写上

// 通过指真修改结构体变量的成员的值 void f(struct Student pst){ (pst).sid = 1001; // pst 为指针变量 (*pst)就是指针pst指向的那个变量 strcpy(pst->name,”张三”); pst->age = 20;
}

// 直接传递结构体变量的方式 // 打印结构体变量 这种在函数列表里 直接传递struct Student st 这种类型的结构体变量,对内存的消耗很大,因为struct Studen // 这个结构体他需要的内催空间为两个int型变量的空间加上一个长度为50的char类型的空间,所以这种传参的方式会在函数调用的时候 // 需要向计算机内存空间申请58个字节的空间,所以我们应该把这个参数写成用指针的形式,因为32位的电脑的话指针固定只占4个字节 void g(struct Student st){ printf(“%d, %s, %d\n”,st.sid,st.name,st.age); }

// 用指针的方式打印 void pg(struct Student *pst){ printf(“%d, %s, %d\n”,pst->sid,pst->name,pst->age); }

int main(void){ // 定义struct Student类型的结构体变量 struct Student st; // 定义结构体变量 st 给结构体变量st分配内存空间

  1. f(&st);
  2. printf("%d, %s, %d\n",st.sid,st.name,st.age);
  3. g(st);
  4. pg(&st);
  5. return 0;

}

  1. <a name="1Gktr"></a>
  2. ## 1.5 动态内存的分配和释放
  3. 动态内存分配和释放(动态分配的内存一定要手动释放,否则造成内存泄露。)
  4. <a name="5hJ4K"></a>
  5. ### 1.5.1 malloc(需要的字节数)动态内存分配
  6. <a name="mYjjN"></a>
  7. #### 1.5.1.1 code1:malloc()简单示例
  8. ```c
  9. # include <stdio.h>
  10. # include <malloc.h>
  11. // malloc(需要的字节数)动态内存分配
  12. int main(void){
  13. int a[5] = {4,10,2,8,6}; // 静态内存分配,数组a固定了长度为5,给数组a分配了20个字节,程序运行期间不能动态增加
  14. int len;
  15. printf("请输入你需要分配的数组的长度:len=");
  16. scanf("%d",&len);
  17. // sizeof(int),代表求的是int类型所占的字节数 在乘以 len 的长度就可以得到需要的内存大小
  18. // malloc()函数只返回获得的存储空间的第一个字节的地址
  19. // 所以(int *) 则表示通过malloc()获得的这么多个字节的空间用来分配给什么类型的变量来使用
  20. // 这里就表示分配给int类型的变量来使用,
  21. // 那么就可以通过获得总共空间的字节数,除以类型的字节大小,就可以知道可以存多少该类型的变量了
  22. int *pArr = (int *)malloc(sizeof(int) * len);
  23. *pArr = 4; // 假设有一个数组叫做Arr,则*pArr = 4 就类似于 Arr[0] = 4
  24. pArr[1] = 10; // 就类类似于Arr[1] = 10
  25. *(pArr+2) = 5; // 就类似于Arr[2] =5,这个时候其实pArr就可以当做数组名来使用,因为它*pArr指针变量就指向了malloc()获得的空间的第一个位置
  26. printf("%d %d",*pArr,pArr[2]);
  27. // 这个时候就就可以把pArr当做一个数组来使用
  28. for(int i=0; i<len; i++)
  29. pArr[i] = i*i;
  30. for(int i=0; i<len; i++)
  31. printf("%d\n",*(pArr+i));
  32. free(pArr); // 主动释放内存
  33. return 0;
  34. }

1.5.1.2 深刻理解动态内存的申请和释放

  1. # include <stdio.h>
  2. # include <malloc.h>
  3. void main(void){
  4. int *pArr = (int *)malloc(sizeof(int)*4);
  5. printf("打印指针地址:%d\n",pArr);
  6. printf("打印指针长度:%d\n",sizeof(pArr));
  7. for (int i=0; i<4; i++)
  8. pArr[i] = i;
  9. printf("打印指针的第一个数:%d\n",pArr[0]);
  10. free(pArr);
  11. printf("释放内存后打印指针的第一个数:%d\n",pArr[0]);
  12. printf("释放内存后打印指针地址:%d\n",pArr);
  13. printf("释放内存后打印指针长度:%d\n",sizeof(pArr));
  14. return 0;
  15. }

1.5.2 跨函数使用内存

1.5.2.1 静态局部变量

  1. # include <stdio.h>
  2. int f(){
  3. int j = 20;
  4. return j;
  5. }
  6. int main(void){
  7. int i = 10;
  8. i = f();
  9. printf("i=%d\n",i);
  10. /*
  11. 在静态变量的情况下,主调函数调用完背调函数,被调函数中的变量就会被操作系统释放
  12. 如果是在动态分配内存的情况下,如果不在背调函数中主动释放内存,
  13. 那么当主调函数调用完了被调函数,背调函数中的局部变量将继续在内存中,
  14. 不会被操作系统收回
  15. */
  16. for(int i=0; i<200; i++)
  17. f();
  18. return 0;
  19. }

1.5.2.2 跨函数使用内存

  1. # include <stdio.h>
  2. # include <malloc.h>
  3. struct Student{
  4. int sid;
  5. int age;
  6. };
  7. struct Student * creatStudent(){
  8. struct Student *ps = (struct Student *)malloc(sizeof(struct Student));
  9. ps->age = 20;
  10. ps->sid = 1001;
  11. return ps;
  12. }
  13. void showStudent(struct Student *ps){
  14. printf("%d %d\n",ps->sid,ps->age);
  15. }
  16. // 跨函数使用内存
  17. void main(void){
  18. struct Student *ps;
  19. ps = creatStudent();
  20. showStudent(ps);
  21. free(ps);
  22. printf("%d",ps);
  23. return 0;
  24. }

1.6 typedef 别名

  1. /*
  2. * @Descripttion:
  3. * @version: Beta-v1.0.0
  4. * @Author: jhong.tao
  5. * @Date: 2020-11-29 18:52:00
  6. * @LastEditTime: 2020-11-29 19:03:06
  7. */
  8. # include <stdio.h>
  9. typedef struct Student{
  10. int sid;
  11. char name[40];
  12. char sex;
  13. }* PST,ST; // PST 等价于 struct Student * , 也就是说PST为一个结构体指针类型;ST等价于struct Student
  14. int main(void){
  15. // struct Student st;
  16. ST st; // 等价于struct Student st;
  17. PST ps = &st; // 等价于 struct Student * ps
  18. ps->sid =1001;
  19. printf("%d",st.sid);
  20. return 0;
  21. }

2. C语言C如何在一个文件里调用另一个源文件中的函数

当程序大了代码多了之后,想模块化开发,不同文件中存一点,是很好的解决办法,那我们如何做才能让各个文件中的代码协同工作呢?我们知道,main函数是程序入口,我们希望把不同的功能写在不同的函数中,并把这些函数统一放到另外一个文件里,以便main函数显得太长,main函数可以在用到某方法的时候调用来处理。为了实现这个步骤,我们这样做。
首先定义一个c代码的头文件,如function.h,在里面声明将要实现的函数,如int add(int a,int b);
然后新建一个源文件为function.c,在function.c的开头#include “function.h”,然后下面写头文件中已声明的函数的实现。
这样写完了之后,main函数如果要调用这个源文件中的函数,只需要在main函数的开头部分加入#include,如此这般,main函数调用相应函数的时候就会自动找到程序的实现部分代码了。

2.1 实现步骤

  1. 创建头文件:function.h ```c // 文件名:function.h

    include

int add(int a,int b);

  1. 2. 创建工具函数文件:function.c
  2. ```c
  3. // 文件名function.c
  4. #include<function.h>
  5. int add(int a,int b){
  6. return a+b;
  7. }
  1. main()调用:tes.c ```c // 文件名:test.c

    include

    include // 引用自创的工具类库function.c文件

int main(){ int a = 1,b =2; int c = add(a,b); //这里是对function.c中的add函数的调用 printf(“c=%d”,c);

  1. return 0;

} ```