C高级

指针

  1. 指针:轻量级、

  2. 指针定义:

    • 存储类型 数据类型 *指针变量名;
  3. 指针初始化:

    • 存储类型 数据类型 *指针变量名=地址;
  4. 指针赋值:

    • 把一个已知变量的地址赋值给指针
    • 把已知数组首地址赋值给指针
    • 把同级指针赋值给指针
    • 把NULL赋值给指针

函数

  • 完成特点的程序模块
  • 函数的分类
    • 库函数:给用户直接调用的函数(调用时需要加对应的头文件)
    • 自定义函数:用户中间写的函数

函数指针数组

本质是一个数组,只是这个数组里面放的是指向函数的指针

一般形式:存储类型 数据类型 (*数组名【下标】)(数据类型 形参1,数据类型 形参2.。。。)

例如:int (*p【3】)(int ,int);

#include <stdio.h>
int add(int a,int b)
{
    return a+b;
}
int jian(int a,int b)
{
    return a-b;
}
int cheng(int a,int b)
{
    return a*b;
}
int chu(int a,int b)
{
    return a/b;
}
int main(int argc, char *argv[])
{ 
    int (*p[4])(int a,int b)={add,jian,cheng,chu};
    int i,j;
    char c;
    scanf("%d%c%d",&i,&c,&j);
    switch(c)
    {
        case '+':printf("%d\n",p[0](i,j));break;
        case '-':printf("%d\n",p[1](i,j));break;
        case '*':printf("%d\n",p[2](i,j));break;
        case '/':printf("%d\n",p[3](i,j));break;
    }

    return 0;
} 

函数的多文件封装

  1. 头文件–可以放库头文件和所有功能函数的声明

    #ifndef _JISUAN_H ---- 防止头文件重复包含
    #define _JISUAN_H
    #include <stdio.h> ---- 库头文件
    int add(int a,int b); ---- 函数的声明
    #endif
    
  2. 功能文件— 自定义的头文件和所有功能函数代码

    #include "jisuan.h"
    int add(int a,int b)
     {
         return a+b;
     }
     int jian(int a,int b)
     {
         return a-b;
     }
     int cheng(int a,int b)
     {
         return a*b;
     }
     int chu(int a,int b)
     {
         return a/b;
     }
    
  3. 主函数文件—自定义头文件和函数的调用

     #include "jisuan.h"
     int main(int argc, char *argv[])
     {
         int x=12,y=34;
         printf("sum=%d\n",add(x,y)); 
         return 0;
     }
    编译:至少编译两个.c文件,头文件默认在当前目录下去找
        调用外部头文件:-I
    

递归函数

  • 直接或间接的调用函数本身的函数
  • 两个条件:调用自己,要有结束表示
  • 每次调用自己都会给函数开辟空间,所以每次调用的变量都会被保留在当前函数内,
  • 循环能做的递归能做,但递归能做的循环不能做

回调函数

  • 把函数作为一个参数,用函数指针来接受的函数

    #include <stdio.h>
    int add(int a,int b){
        return a+b;
    }
    int sub(int a,int b){
        return a-b;
    }
    int mul(int a,int b){
        return a * b;
    }
    int div(int a,int b){
        return a / b;
    }
    int calc(int a,int b,int(*p)(int a,int b)){
        return p(a,b);
    }
    int main(){
    int a=21,b=7;
        printf("result = %d",calc(a,b,sub));
    }
    

动态开辟空间函数

malloc

  • 在堆区中开辟空间,需要我们手动开辟,也需要我们手动释放

    //头文件
    #include<stdlib.h>
    //函数原型
    void *malloc(size_t size);
    //参数  size:需要开辟空间的大小
    //返回值:开辟成功返回开辟的地址,失败返回空
    //释放空间地址:
    void free(void *ptr);
    //参数:ptr:要释放的首地址,是开辟的指针并不是指针本身所在的栈空间
    //最后为了避免野指针,所以需要把NULL赋值给指针
    
  • 练习

    #include <stdio.h>
    #include <stdlib.h>
    int main(){
        int *p=(int *)malloc(sizeof (int)*10);
        int *pp=p;
        for (int i = 0; i < 10; ++i) {
            *p=rand();
            p++;
        }
        for (int i = 0; i < 10; ++i) {
            printf("%d ",*pp);
            pp++;
        }
        free(p);
        p=NULL;
        pp=NULL;
        return 0;
    }
    

关键字

const — 变量常量化

  • const修饰指针
  • int const *p =&a —p的内容不能改
  • int *const p =&a —p的地址不能改

define —宏定义

 #include <stdio.h>
 #define A char *
 typedef  char * B;
 int main(int argc, char *argv[])
 {
     A a,b;
     B c,d;
     printf("a=%ld\n",sizeof(a));
     printf("b=%ld\n",sizeof(b));
     printf("c=%ld\n",sizeof(c));
     printf("d=%ld\n",sizeof(d));     
     return 0;
 }

image-20230317090936491

上面结果为上图 ,所可以看到b=1,表示把A a,b;中的A替换成了char *, 故为char *a,b;类型为char 而不是char *

typedef ---- 重命名

已知数据类型的重命名
 #include <stdio.h>
 typedef int A;
 int main(int argc, char *argv[])
 {
     A a=12;
     printf("a=%d\n",a);    
     return 0;
 }  
define 和typedef二者区别

image-20230316145602504

  • 练习 — 利用宏定义求出两个数之间的最大值

    #include <stdio.h>
    #define MAX(x,y) x>y?x:y
    int main(){
        int a=5,b=7;
        printf("%d\n",MAX(a,b));
    }
    

static —静态区域存储

作用

  • 修饰全局变量,限制作用域
  • 修饰局部变量,延长生命周期(只会被初始化一次)
  • 修饰函数,限制作用域

extern — 调用外部文件变量

  • 只能调用全局变量

  • 编译的时候要和调用的文件一起编译

#extern2.c
int a=88;

#extern1.c
#include <stdio.h>
extern int a;
int main(int argc,char *argv[]){
    printf("%d\n",a);
    return 0;
}
//编译时要同时都要编译
gcc exten1.c extern2.c
./a.out
//输出结果为
88

struct — 结构体

  • 本质还是一个数据类型,只是里面可以放很多成员,这些成员数据类型可以医院也可以不一样,也可以是已知的数据类型,也可以是构造类型

  • struct 结构体名{
        数据类型 成员1;
        数据类型 成员2;
        ....
    }; ---分号不能省
    
  • 全局初始化:在头文件下,主函数上,就是在构造完这个结构体后立马初始化

  • 局部初始化:在函数体中定义一个结构体变量

    image-20230316154934228

  • 结构体赋值

    1. 结构体赋结构体
    2. 成员单独赋值
      • 注意给字符数组赋值用strcpy()赋值
  • ⭐️结构体大小(笔试会考)

    • 如果是64位系统系统默认安装8byte对齐,但是如果最大的数据类型小于8byte,就按照最大成员的数据类型的长度去计算(就是最大成员变量的倍数)
    • 如果是32位系统系统默认安装4byte对齐,但是如果最大的数据类型小于4byte,就按照最大成员的数据类型的长度去计算(就是最大成员变量的倍数)
    • 偶数地址存储
    • 如果对齐自己能够放下后面的成员,就会在当前字节放,否则就空出来重新开辟

结构体嵌套

练习

#include <stdio.h>
typedef struct stu{
 int s_id;
 char std_name[20];
}STU;
typedef struct tea{
 int t_id;
 char tea_name[20];
 STU stus[20];
}TEA;
int main() {
 STU stu1={10001,"张三"};
 STU stu2={10002,"李四"};
 STU stu3={10003,"王五"};
 TEA tea={10001,"zzz",{stu1,stu2,stu3}};
 printf("the teacher name is %s\n",tea.tea_name);
 printf("the teacher have this students\n");
 for (int i = 0; i < 3; ++i) {
     printf("NO.%d student's name is %s\n",i,tea.stus[i].std_name);
 }
return 0;
}

结构体指针

  • 本质是一个指针,只是这个指针指向了一个结构体
  //一般形式
      struct 结构体名 * 指针变量;
  //初始化
      struct 结构体名 * 指针变量名=结构体地址;
  //访问内容,两种方式
  (*p).成员
  p->成员

练习

 #include <stdio.h>
 typedef struct stu{
  int s_id;
  char std_name[20];
 }STU;
 typedef struct tea{
  int t_id;
  char tea_name[20];
  STU stus[20];
 }TEA;
 int main() {
  STU stu1={10001,"张三"};
  STU stu2={10002,"李四"};
  STU stu3={10003,"王五"};
  TEA tea={10001,"zzz",{stu1,stu2,stu3}};
  struct tea *p_tea=&tea;
  printf("the teacher name is %s\n",p_tea->tea_name);
  printf("the teacher have this students\n");
  for (int i = 0; i < 3; ++i) {
      printf("NO.%d student's name is %s\n",i,p_tea->stus[i].std_name);
  }
 return 0;
 }

结构体数组

  • 本质是一个数组,只是这个数组里面的元素都是结构体

    //一般形式
        struct 结构体名 变量名[下标] ;
     int main(int argc, char *argv[])
     {   
         STU a={12,"zhangsan",45};
         STU b={13,"lisi",47};
         STU c={15,"wangwu",50};
         STU s[3]={a,b,c};//初始化
         printf("s[1].name=%s\n",s[1].name);
         
         return 0;
     } 
    

共用体 ---- union

  • 成员们一起用一片空间地址

    • 共用体不能初始化

    • 赋值只能成员赋值,而且每次只能赋值一个,前面的赋值都会被覆盖

      image-20230317105632526

    共同体的大小:

    • 也要遵循结构体大小的规则,但是只会给最大成员开辟对于的空间地址

枚举型 ---- enum成员没有初始化,从0开始往后面赋值

  • 如果有成员赋值,成员后面一次递增+1
  • 成员都是常量,不能修改它的值
练习
  • 一周的星期几
#include <stdio.h>
int main(){
    enum monday=1,thursday,tuesday,friday,wednesday,saturday,sunday};
    printf("friday is %d\n",friday);
   return 0;
}

gdb调式工具

  • 帮我们找出代码的问题

使用方法

  1. gcc -g 编译的文件名 ,回生产一个a.out的执行文件 这个文件就可以调式
  2. gdb a.out

选项

  1. r ---- 运行程序
  2. l ---- 查看程序代码
  3. b ---- 设置断点,让程序跑到设置那一行
  4. c ---- 继续运行程序,直到下一个断点,如果没有直接运行完
  5. p 变量名 ---- 查看变量值
  6. n ---- 一行一行的运行(next),但是不进入函数
  7. s ---- 按行执行,但会进入函数
  8. delete ---- 删除所有断点
  9. q ---- 退出
命令 简介 gdb功能 使用方法及备注
run r 运行 调试开始
break b 设置断点 b断点处
info i 查看信息 查看断点i b,等后面详细列举
delete d 删除断点 delete断点编号
disable disable 禁用断点 disable断点编号
backtrace bt,where 查看栈帧 bt N显示开头N个栈帧, bt -N最后N个栈帧
print p 打印变量 p argc打印变量,后面详细介绍
x x 显示内存 x 0x1234567,后面详细介绍
set set 改变变量值 set variable <变量> = <表达式>;比如 set var test=3
next n 执行下一行 n;执行到下一行,不管下一行多复杂
step s 执行下一行 s;若下一行为函数,则进入函数内部
continue c,cont 继续 c为继续的次数,可省略,表示继续一次
finish finish 执行完成当前函数
until until 执行完成代码块