C 语言高级3--函数指针回调函数,预处理,动态库的封装

目录

1.函数指针和回调函数

1.1 函数指针

1.1.1 函数类型

1.1.2 函数指针(指向函数的指针)

1.1.3 函数指针数组

       1.1.4 函数指针做函数参数(回调函数)

 2.预处理

2.1 预处理的基本概念

2.2 文件包含指令(#include)

2.2.1 文件包含处理

 2.2.2 #incude<>和#include""区别

2.3 宏定义

2.3.1 无参数的宏定义(宏常量)

2.3.2 带参数的宏定义(宏函数)

2.4 条件编译

2.4.1 基本概念

 2.4.2 条件编译

2.5 一些特殊的预定宏

3.动态库的封装和使用

3.1 库的基本概念

3.2 windows下静态库创建和使用

3.2.1 静态库的创建

3.2.2 静态库的使用

3.3 静态库优缺点

3.4 windows下动态库创建和使用

3.5 动态库的创建

3.6  动态库的使用

4. 递归函数

4.1 递归函数基本概念

4.2 普通函数调用

 4.3 递归函数调用

 4.4 递归实现字符串反转


1.函数指针和回调函数

1.1 函数指针

1.1.1 函数类型

通过什么来区分两个不同的函数?

一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址。

函数三要素: 名称、参数、返回值。C语言中的函数有自己特定的类型。

c语言中通过typedef为函数类型重命名:

typedef int f(int, int); // f 为函数类型

typedef void p(int); // p 为函数类型

这一点和数组一样,因此我们可以用一个指针变量来存放这个入口地址,然后通过该指针变量调用函数。

注意:通过函数类型定义的变量是不能够直接执行,因为没有函数体。只能通过类型定义一个函数指针指向某一个具体函数,才能调用。

typedef int(p)(int, int);void my_func(int a,int b){printf("%d %d\n",a,b);
}void test(){p p1;//p1(10,20); //错误,不能直接调用,只描述了函数类型,但是并没有定义函数体,没有函数体无法调用p* p2 = my_func;p2(10,20); //正确,指向有函数体的函数入口地址
}

 

1.1.2 函数指针(指向函数的指针)

  1.  函数指针定义方式(先定义函数类型,根据类型定义指针变量);
  2.  先定义函数指针类型,根据类型定义指针变量;
  3.  直接定义函数指针变量;
int my_func(int a,int b){printf("ret:%d\n", a + b);return 0;
}//1. 先定义函数类型,通过类型定义指针
void test01(){typedef int(FUNC_TYPE)(int, int);FUNC_TYPE* f = my_func;//如何调用?(*f)(10, 20);f(10, 20);
}//2. 定义函数指针类型
void test02(){typedef int(*FUNC_POINTER)(int, int);FUNC_POINTER f = my_func;//如何调用?(*f)(10, 20);f(10, 20);
}//3. 直接定义函数指针变量
void test03(){int(*f)(int, int) = my_func;//如何调用?(*f)(10, 20);f(10, 20);
}

1.1.3 函数指针数组

函数指针数组,每个元素都是函数指针。

void func01(int a){printf("func01:%d\n",a);
}
void func02(int a){printf("func02:%d\n", a);
}
void func03(int a){printf("func03:%d\n", a);
}void test(){#if 0//定义函数指针void(*func_array[])(int) = { func01, func02, func03 };
#elsevoid(*func_array[3])(int);func_array[0] = func01;func_array[1] = func02;func_array[2] = func03;
#endiffor (int i = 0; i < 3; i ++){func_array[i](10 + i);(*func_array[i])(10 + i);}
}

       1.1.4 函数指针做函数参数(回调函数)

函数参数除了是普通变量,还可以是函数指针变量。

//形参为普通变量
void fun( int x ){}
//形参为函数指针变量
void fun( int(*p)(int a) ){}

函数指针变量常见的用途之一是把指针作为参数传递到其他函数,指向函数的指针也可以作为参数,以实现函数地址的传递。

int plus(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 division(int a, int b)
{return a / b;
}//函数指针 做函数的参数 --- 回调函数
void Calculator(int(*myCalculate)(int, int), int a, int b)
{int ret = myCalculate(a, b); //dowork中不确定用户选择的内容,由后期来指定运算规则printf("ret = %d\n", ret);
}void test01()
{printf("请输入操作符\n");printf("1、+ \n");printf("2、- \n");printf("3、* \n");printf("4、/ \n");int select = -1;scanf("%d", &select);int num1 = 0;printf("请输入第一个操作数:\n");scanf("%d", &num1);int num2 = 0;printf("请输入第二个操作数:\n");scanf("%d", &num2);switch (select){case  1:Calculator(plus, num1, num2);break;case  2:Calculator(sub, num1, num2);break;case 3:Calculator(mul, num1, num2);break;case 4:Calculator(division, num1, num2);break;default:break;}}

注意:函数指针和指针函数的区别:

        1函数指针是指向函数的指针;

        2指针函数是返回类型为指针的函数;

 2.预处理

2.1 预处理的基本概念

C语言对源程序处理的四个步骤:预处理、编译、汇编、链接。

预处理是在程序源代码被编译之前,由预处理器(Preprocessor)对程序源代码进行的处理。这个过程并不对程序的源代码语法进行解析,但它会把源代码分割或处理成为特定的符号为下一步的编译做准备工作。

2.2 文件包含指令(#include)

2.2.1 文件包含处理

“文件包含处理”是指一个源文件可以将另外一个文件的全部内容包含进来。C语言提供了#include命令用来实现“文件包含”的操作。

 2.2.2 #incude<>和#include""区别

  1. "" 表示系统在file1.c所在的当前目录找file1.h,如果找不到,按系统指定的目录检索。
  2. < > 表示系统直接按系统指定的目录检索。

注意:

1. #include <>常用于包含库函数的头文件;

2. #include ""常用于包含自定义的头文件;

3. 理论上#include可以包含任意格式的文件(.c .h等) ,但一般用于头文件的包含;

2.3 宏定义

2.3.1 无参数的宏定义(宏常量)

如果在程序中大量使用到了100这个值,那么为了方便管理,我们可以将其定义为:

const int num = 100; 但是如果我们使用num定义一个数组,在不支持c99标准的编译器上是不支持的,因为num不是一个编译器常量,如果想得到了一个编译器常量,那么可以使用:

#define num 100

在编译预处理时,将程序中在该语句以后出现的所有的num都用100代替。这种方法使用户能以一个简单的名字代替一个长的字符串,在预编译时将宏名替换成字符串的过程称为“宏展开。宏定义,只在宏定义的文件中起作用。

#define PI 3.1415
void test(){double r = 10.0;double s = PI * r * r;printf("s = %lf\n", s);
}

 

说明:

  1. 宏名一般用大写,以便于与变量区别;
  2. 宏定义可以是常数、表达式等;
  3. 宏定义不作语法检查,只有在编译被宏展开后的源程序才会报错;
  4. 宏定义不是C语言,不在行末加分号;
  5. 宏名有效范围为从定义到本源文件结束;
  6. 可以用#undef命令终止宏定义的作用域;

在宏定义中,可以引用已定义的宏名;

2.3.2 带参数的宏定义(宏函数)

在项目中,经常把一些短小而又频繁使用的函数写成宏函数,这是由于宏函数没有普通函数参数压栈、跳转、返回等的开销,可以调高程序的效率。宏通过使用参数,可以创建外形和作用都与函数类似地类函数宏(function-like macro). 宏的参数也用圆括号括起来。

#define SUM(x,y) (( x )+( y ))
void test(){//仅仅只是做文本替换 下例替换为 int ret = ((10)+(20));//不进行计算int ret = SUM(10, 20);printf("ret:%d\n",ret);
}

注意:

  1. 宏的名字中不能有空格,但是在替换的字符串中可以有空格。ANSI C允许在参数列表中使用空格;
  2. 用括号括住每一个参数,并括住宏的整体定义。
  3. 用大写字母表示宏的函数名。
  4. 如果打算宏代替函数来加快程序运行速度。假如在程序中只使用一次宏对程序的运行时间没有太大提高。

2.4 条件编译

2.4.1 基本概念

一般情况下,源程序中所有的行都参加编译。但有时希望对部分源程序行只在满足一定条件时才编译,即对这部分源程序行指定编译条件。

 2.4.2 条件编译

防止头文件被重复包含引用;

#ifndef _SOMEFILE_H
#define _SOMEFILE_H//需要声明的变量、函数
//宏定义
//结构体#endif

2.5 一些特殊的预定宏

C编译器,提供了几个特殊形式的预定义宏,在实际编程中可以直接使用,很方便。

//	__FILE__			宏所在文件的源文件名 
//	__LINE__			宏所在行的行号
//	__DATE__			代码编译的日期
//	__TIME__			代码编译的时间void test()
{printf("%s\n", __FILE__);printf("%d\n", __LINE__);printf("%s\n", __DATE__);printf("%s\n", __TIME__);
}

3.动态库的封装和使用

3.1 库的基本概念

库是已经写好的、成熟的、可复用的代码。每个程序都需要依赖很多底层库,不可能每个人的代码从零开始编写代码,因此库的存在具有非常重要的意义。在我们的开发的应用中经常有一些公共代码是需要反复使用的,就把这些代码编译为库文件。库可以简单看成一组目标文件的集合,将这些目标文件经过压缩打包之后形成的一个文件。像在Windows这样的平台上,最常用的c语言库是由集成按开发环境所附带的运行库,这些库一般由编译厂商提供。

3.2 windows下静态库创建和使用

3.2.1 静态库的创建

1. 创建一个新项目,在已安装的模板中选择“常规”,在右边的类型下选择“空项目”,在名称和解决方案名称中输入staticlib。点击确定。

2.在解决方案资源管理器的头文件中添加,mylib.h文件,在源文件添加mylib.c文件(即实现文件)。

3.在mylib.h文件中添加如下代码:

#ifndef TEST_H
#define TEST_Hint myadd(int a,int b);
#endif

4.在mylib.c文件中添加如下代码:

        

#include"test.h"
int myadd(int a, int b){return a + b;
}

5. 配置项目属性。因为这是一个静态链接库,所以应在项目属性的“配置属性”下选择“常规”,在其下的配置类型中选择“静态库(.lib)。

6.编译生成新的解决方案,在Debug文件夹下会得到mylib.lib (对象文件库),将该.lib文件和相应头文件给用户,用户就可以使用该库里的函数了。

3.2.2 静态库的使用

方法一:配置项目属性

A、添加工程的头文件目录:工程---属性---配置属性---c/c++---常规---附加包含目录:加上头文件存放目录。

B、添加文件引用的lib静态库路径:工程---属性---配置属性---链接器---常规---附加库目录:加上lib文件存放目录。

C  然后添加工程引用的lib文件名:工程---属性---配置属性---链接器---输入---附加依赖项:加上lib文件名。

 方法二:使用编译语句

#pragma comment(lib,"./mylib.lib")

 方法三:添加工程中

就像你添加.h和.c文件一样,把lib文件添加到工程文件列表中去.

切换到"解决方案视图",--->选中要添加lib的工程-->点击右键-->"添加"-->"现有项"-->选择lib文件-->确定.

3.3 静态库优缺点

  1. 静态库对函数库的链接是放在编译时期完成的,静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系;
  2. 程序在运行时与函数库再无瓜葛,移植方便。
  3. 浪费空间和资源,所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。

内存和磁盘空间

静态链接这种方法很简单,原理上也很容易理解,在操作系统和硬件不发达的早期,绝大部门系统采用这种方案。随着计算机软件的发展,这种方法的缺点很快暴露出来,那就是静态链接的方式对于计算机内存和磁盘空间浪费非常严重。特别是多进程操作系统下,静态链接极大的浪费了内存空间。在现在的linux系统中,一个普通程序会用到c语言静态库至少在1MB以上,那么如果磁盘中有2000个这样的程序,就要浪费将近2GB的磁盘空间。

 

程序开发和发布

空间浪费是静态链接的一个问题,另一个问题是静态链接对程序的更新、部署和发布也会带来很多麻烦。比如程序中所使用的mylib.lib是由一个第三方厂商提供的,当该厂商更新容量mylib.lib的时候,那么我们的程序就要拿到最新版的mylib.lib,然后将其重新编译链接后,将新的程序整个发布给用户。这样的做缺点很明显,即一旦程序中有任何模块更新,整个程序就要重新编译链接、发布给用户,用户要重新安装整个程序。

3.4 windows下动态库创建和使用

要解决空间浪费和更新困难这两个问题,最简单的办法就是把程序的模块相互分割开来,形成独立的文件,而不是将他们静态的链接在一起。简单地讲,就是不对哪些组成程序的目标程序进行链接,等程序运行的时候才进行链接。也就是说,把整个链接过程推迟到了运行时再进行,这就是动态链接的基本思想。

3.5 动态库的创建

1. 创建一个新项目,在已安装的模板中选择“常规”,在右边的类型下选择“空项目”,在名称和解决方案名称中输入mydll。点击确定。

2.在解决方案资源管理器的头文件中添加,mydll.h文件,在源文件添加mydll.c文件(即实现文件)。

3.在test.h文件中添加如下代码:

#ifndef TEST_H
#define TEST_H__declspec(dllexport) int myminus(int a, int b);#endif

test.c文件中添加如下代码:

#include"test.h"
__declspec(dllexport) int myminus(int a, int b){return a - b;
}

5. 配置项目属性。因为这是一个态链接库,所以应在项目属性的“配置属性”下选择“常规”,在其下的配置类型中选择“态库(.dll)。

6.编译生成新的解决方案,在Debug文件夹下会得到mydll.dll (对象文件库),将该.dll文件、.lib文件和相应头文件给用户,用户就可以使用该库里的函数了。

疑问一:__declspec(dllexport)是什么意思?

动态链接库中定义有两种函数:导出函数(export  function)和内部函数(internal  function)。 导出函数可以被其它模块调用,内部函数在定义它们的DLL程序内部使用。

疑问二:动态库的lib文件和静态库的lib文件的区别?

在使用动态库的时候,往往提供两个文件:一个引入库(.lib)文件(也称“导入库文件”)和一个DLL(.dll)文件。虽然引入库的后缀名也是“lib”,但是,动态库的引入库文件和静态库文件有着本质的区别,对一个DLL文件来说,其引入库文件(.lib)包含该DLL导出的函数和变量的符号名,而.dll文件包含该DLL实际的函数和数据。在使用动态库的情况下,在编译链接可执行文件时,只需要链接该DLL的引入库文件,该DLL中的函数代码和数据并不复制到可执行文件,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间中,然后访问DLL中导出的函数。

3.6  动态库的使用

方法一:隐式调用

     创建主程序TestDll,将mydll.h、mydll.dll和mydll.lib复制到源代码目录下。

(P.S:头文件Func.h并不是必需的,只是C++中使用外部函数时,需要先进行声明)

在程序中指定链接引用链接库 : #pragma comment(lib,"./mydll.lib")

方法二:显式调用

HANDLE hDll; //声明一个dll实例文件句柄

hDll = LoadLibrary("mydll.dll"); //导入动态链接库

MYFUNC minus_test; //创建函数指针

//获取导入函数的函数指针

minus_test = (MYFUNC)GetProcAddress(hDll, "myminus");

 

4. 递归函数

4.1 递归函数基本概念

C通过运行时堆栈来支持递归函数的实现。递归函数就是直接或间接调用自身的函数。

4.2 普通函数调用

void funB(int b){printf("b = %d\n", b);
}void funA(int a){funB(a - 1);printf("a = %d\n", a);
}int main(void){funA(2);printf("main\n");return 0;
}

函数的调用流程如下:

 4.3 递归函数调用

void fun(int a){if (a == 1){printf("a = %d\n", a);return; //中断函数很重要}fun(a - 1);printf("a = %d\n", a);
}int main(void){fun(2);printf("main\n");return 0;
}

函数的调用流程如下:

 

作业:

递归实现给出一个数8793,依次打印千位数字8、百位数字7、十位数字9、个位数字3。

void recursion(int val){

if (val == 0){

return;

}

int ret = val / 10;

recursion(ret);

printf("%d ",val % 10);

}

 4.4 递归实现字符串反转

int reverse1(char *str){if (str == NULL){return -1;}if (*str == '\0') // 函数递归调用结束条件{return 0;}reverse1(str + 1);printf("%c", *str);return 0;
}char buf[1024] = { 0 };  //全局变量int reverse2(char *str){if (str == NULL) {return -1;}if ( *str == '\0' ) // 函数递归调用结束条件{return 0;}reverse2(str + 1);strncat(buf, str, 1);return 0;
}int reverse3(char *str, char *dst){if (str == NULL || dst == NULL) {return -1;}if (*str == '\0') // 函数递归调用结束条件{return 0;}reverse3(str + 1);strncat(dst, str, 1);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/29295.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ 线性群体的概念

线性群体中的元素次序与其位置关系是对应的。 在线性群体中&#xff0c;可以按照访问元素的不同方法分为直接访问、顺序访问和索引访问。 &#xff08;1&#xff09;直接访问 对可直接访问的线性群体&#xff0c;我们可以直接访问群体中的任何一个元素&#xff0c;而不必首先访…

npm 报错 cb() never called!

不知道有没有跟我一样的情况&#xff0c;在使用npm i的时候一直报错&#xff1a;cb() never called! 换了很多个node版本&#xff0c;还是不行&#xff0c;无法解决这个问题 百度也只是让降低node版本请缓存&#xff0c;gpt给出的解决方案也是同样的 但是缓存清过很多次了&a…

Python中enumerate用法详解

目录 1.简介 2.语法 3.参数 4.返回值 5.详解 6.实例 7.补充 1.简介 enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列&#xff0c;同时列出数据和数据下标&#xff0c;一般用在 for 循环当中。 2.语法 以下是 enumerate() 方法的语…

Linux 匿名页的生命周期

目录 匿名页的生成 匿名页生成时的状态 do_anonymous_page缺页中断源码 从匿名页加入Inactive lru引出 一个非常重要内核patch 匿名页何时回收 本文以Linux5.9源码讲述 匿名页的生成 用户空间malloc/mmap(非映射文件时&#xff09;来分配内存&#xff0c;在内核空间发生…

【小梦C嘎嘎——启航篇】类和对象(中篇)

【小梦C嘎嘎——启航篇】类和对象&#xff08;中篇&#xff09;&#x1f60e; 前言&#x1f64c;类的6个默认成员函数构造函数析构函数拷贝构造函数拷贝构造函数的特性有哪些&#xff1f;既然编译器可以自动生成一个拷贝构造函数&#xff0c;为什么我们还要自己设计实现呢&…

【腾讯云 Cloud Studio 实战训练营】使用Cloud Studio构建SpringSecurity权限框架

1.Cloud Studio&#xff08;云端 IDE&#xff09;简介 Cloud Studio 是基于浏览器的集成式开发环境&#xff08;IDE&#xff09;&#xff0c;为开发者提供了一个永不间断的云端工作站。用户在使用 Cloud Studio 时无需安装&#xff0c;随时随地打开浏览器就能在线编程。 Clou…

Spring 知识点

Spring 1.1 Spring 简介 1.1.1 Spring 概念 Spring是一个轻量级Java开发框架&#xff0c;最早有Rod Johnson创建为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题Spring最根本的使命是解决企业级应用开发的复杂性&#xff0c;即简化Java开发。使现有的技术更加容易使…

Linux下进程的特点与环境变量

目录 进程的特点 进程特点的介绍 进程时如何实现并发性的 进程间如何切换 概念铺设 PC指针 上下文 环境变量 PATH 修改PATH HOME SHELL env 命令行参数 什么是命令行参数&#xff1f; 打印命令行参数 通过函数获得环境变量 getenv 命令行参数 env 修改环境变…

SpringBoot 项目使用 Redis 对用户 IP 进行接口限流

一、思路 使用接口限流的主要目的在于提高系统的稳定性&#xff0c;防止接口被恶意打击&#xff08;短时间内大量请求&#xff09;。 比如要求某接口在1分钟内请求次数不超过1000次&#xff0c;那么应该如何设计代码呢&#xff1f; 下面讲两种思路&#xff0c;如果想看代码可…

MySql用户管理、权限管理

用户管理 1. 查看系统用户&#xff08;查询mysql系统数据库中的user表&#xff09; select * from mysql.user; 2. 创建用户 CREATE USER 用户名主机名 identified by 密码 -- 创建用户zhonghua,只能在当前主句localhost访问,密码为123456 create user zhonghualocalhost i…

springCache-缓存

SpringCache 简介&#xff1a;是一个框架&#xff0c;实现了基于注解的缓存功能&#xff0c;底层可以切换不同的cache的实现&#xff0c;具体是通过CacheManager接口实现 使用springcache,根据实现的缓存技术&#xff0c;如使用的redis,需要导入redis的依赖包 基于map缓存 …

MySQL 查询语句大全

目录 基础查询 直接查询 AS起别名 去重&#xff08;复&#xff09;查询 条件查询 算术运算符查询 逻辑运算符查询 正则表达式查询⭐ 模糊查询 范围查询 是否非空判断查询 排序查询 限制查询&#xff08;分页查询&#xff09; 随机查询 分组查询 HAVING 高级查询…

EtherCAT转EtherCAT网关FX5U有EtherCAT功能吗两个ETHERCAT设备互联

1.1 产品功能 捷米JM-ECT-ECT是自主研发的一款ETHERCAT从站功能的通讯网关。该产品主要功能是将2个ETHERCAT网络连接起来。 本网关连接到ETHERCAT总线中做为从站使用。 1.2 技术参数 1.2.1 捷米JM-ECT-ECT技术参数 ● 网关做为ETHERCAT网络的从站&#xff0c;可以连接倍福、…

小型双轮差速底盘机器人实现红外跟随功能

1. 功能说明 本文示例将实现R023样机小型双轮差速底盘跟随人移动的功能。在小型双轮差速底盘前方按下图所示安装3个 近红外传感器&#xff0c;制作一个红外线发射源&#xff0c;实现当红外发射源在机器人的检测范围内任意放置或移动时&#xff0c;机器人能追踪该发射源。 2. 电…

抖音seo矩阵系统源码搭建开发详解

抖音SEO矩阵系统是一个用于提高抖音视频在搜索引擎排名的工具。如果你想开发自己的抖音SEO矩阵系统&#xff0c;以下是详细的步骤&#xff1a; 开发步骤详解&#xff1a; 确定你需要的功能和算法 抖音SEO矩阵系统包含很多功能&#xff0c;比如关键词研究、内容优化、链接建设、…

PHP8的跳转语句-PHP8知识详解

如果循环条件满足的时候&#xff0c;则程序会一直执行下去。如果需要强制跳出循环&#xff0c;则需要使用跳转语句来完成。PHP8的跳转语句包括break语句、continue语句和goto语句。 1、break语句 break语句的作用是完全终止循环&#xff0c;包括while、do…while、for、switch…

6. CSS(三)

目录 一、盒子模型 &#xff08;一&#xff09;网页布局的本质 &#xff08;二&#xff09;盒子模型组成 &#xff08;三&#xff09;边框&#xff08;border&#xff09; &#xff08;四&#xff09;表格的细线边框 &#xff08;五&#xff09;内边距&#xff08;padding…

[JavaScript游戏开发] 绘制Q版地图、键盘上下左右地图场景切换

系列文章目录 第一章 2D二维地图绘制、人物移动、障碍检测 第二章 跟随人物二维动态地图绘制、自动寻径、小地图显示(人物红点显示) 第三章 绘制冰宫宝藏地图、人物鼠标点击移动、障碍检测 第四章 绘制Q版地图、键盘上下左右地图场景切换 文章目录 系列文章目录前言一、本章节…

【TypeScript】类型断言-类型的声明和转换(五)

【TypeScript】类型断言-类型的声明和转换&#xff08;五&#xff09; 【TypeScript】类型断言-类型的声明和转换&#xff08;五&#xff09;一、简介二、断言形式2.1 尖括号语法2.2 as形式 三、断言类型3.1 非空断言3.2 肯定断言-肯定化保证赋值3.3 将任何类型断言为any3.4 调…

Django实现音乐网站 ⑹

使用Python Django框架制作一个音乐网站&#xff0c; 本篇主要是在添加编辑过程中对后台歌手功能优化及表模型名称修改、模型继承内容。 目录 表模型名称修改 模型继承 创建抽象基类 其他模型继承 更新表结构 歌手新增、编辑优化 表字段名称修改 隐藏单曲数和专辑数 姓…