C语言重点复习大纲

news2024/9/27 17:37:21

目录

  • 数据存储(3星)
    • 判断大小端
      • 写一个函数判断大小端
    • 截断与整形提升
  • 数组和指针(5星)
    • 几个特殊的指针
    • 数组传参
    • 字符串数组
  • 库函数的实现(4星)
    • atoi与itoa
    • memcpy与memmove
      • 内存重叠
  • 自定义类型(4星)
    • 内存对齐
    • 结构体,联合体,枚举
    • 位段
  • 编译链接(3星)
    • 编译和链接的过程
    • 条件编译
  • 操作符和关键字(4星)
    • volatile
    • const
    • static(结合C++)

数据存储(3星)

判断大小端

在这里插入图片描述

大端

将数据的高位存储在内存的低地址;

小端(常用)

数据的低位存储在内存的低地址

写一个函数判断大小端

强转即可;发生了截断;

bool judge(int i = 1){
   //判断是不是小端
    return char(i);
}

截断与整形提升

截断

    int a = 0x0fffffff;
    char c = a;
    int b = c;

    printf("%d\n", b);// char 1个字节 发生截断,截取低位8个字节(11111111),(注意大小端),输出-1

整形提升

char a = 0xff;//8个1
printf("%d\n",a); //输出-1;a二进制 11111111 ,符号位为1,提升int4字节的时,前面补1!

a= 0x7f;
printf("%d\n",a); //输出127;a二进制 01111111 ,符号位为0,提升int4字节的时,前面补0

数组和指针(5星)

数组

一段连续的内存空间,用于存储若干个指定相同类型的值;

只有在&arr 或者 sizeof(arr)的时候,数组名arr才被看作整个数组,其余情况看做首元素的地址!

指针

指向内存中的一个地址,该地址对应的内存空间下可能存放有特定数据;

几个特殊的指针

注意符号的优先级进行区分!

指针数组

int *arr[NUM]; //[]优先,所以他是一个数组,存储的类型为int*;

数组指针

int (*arr)[NUM]; //()让arr是一个指针,指向的类型为int [NUM]数组 

函数指针

int (*ptr)(int int) //ptr是一个指针,指向int (int int),参数为两个int,返回值为int的函数;
    
//这样比较复杂,一般typedef定义一个新的类型名;
typedef int (*Ptr)(int int) Ptr
Ptr p = Add;//创建实例;
(*p)(1,1);//调用函数Add 返回2

数组传参

一维数组传参相当于退化为指针;

void f1(int arr[])//int *arr
{
	
    cout<<sizeof(arr)<<endl; //输出4
}
int main()
{
    int arr[10];
    f1(arr);
}

二维数组传参必须指定列数(才能确定+,-这些跳过二维中若干个一维的地址偏移操作),相当于数组指针;

void f2(int arr[][4])//int *arr
{
	
    cout<<sizeof(arr)<<endl; //输出4
}
int main()
{
    int arr[4][4];
    f2(arr);
}

字符串数组

假设字符串数组为carr,这里要注意strlen(carr)和sizeof(carr)的区别;

  • 因为strlen计算字符串长度,他会找‘\0’然后停下,所以不会计算’\0’;
  • sizeof则会把’\0’这个特殊字符也算上,所以对于一般的字符串,sizeof(carr) = strlen(carr)+1;

库函数的实现(4星)

atoi与itoa

atoi

int atoi (const char * str);
//将一个字符串转换成int返回;



int My_Atoi(const char* str){
    
    
    int len = strlen(str);
    char tmp[100];
    int index = 0;
    for(int i = len-1;i>=0;i--){
        if(str[i]=='-') continue;//跳过负数,最后再判断;
        tmp[index++] = str[i];
    }
    int ret = 0;
    while(len)
    {
        int c = tmp[len-1] - '0';
        ret*=10;
        ret+=c;
        len--;
    }
    
   //判断负数情况
    if(str[0] == '-') ret*=-1;
    return ret;
}

itoa

char *  itoa ( int value, char * str, int base );
//将一个整形转换成对应进制的字符串; 进制转换<-->辗转相除;





void Swap(char* a, char* b)
{
    char tmp = *a;
    *a = *b;
    *b = tmp;
}
char* My_Itoa(int value, char* str, int base)
{
    char ret[1024];
    int index = 0;
    int flag = 0;
    char carr[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
    //处理负数用'-'加原码表示,暂时不考虑补码这些;
    if (value < 0) {
        flag = 1;
        value *= -1;
    }

    while (value) 
    {
        ret[index++] = carr[value % base];
        value /= base;
    }
    if (flag) {
        ret[index++] = '-';
    }
     
    //翻转
    int l = 0;
    int r = index - 1;
    while (l < r)
    {
        Swap(&ret[l],&ret[r]);
        l++;
        r--;
    }
   
    ret[index++] = '\0';


    return ret;
}


memcpy与memmove

strcpy用于string的拷贝,遇到’\0’停止,使用仅限于字符串;

二momcpy是void*类型的内存拷贝,适用更多场景;

在C/C++一些笔试中较常见要求不用标准库函数,实现mommove函数的功能,这里进行一下自我总结:

void * memcpy ( void * dest, const void * src, size_t num );
void * memmove ( void * dest, const void * src, size_t num );
  • dest 目的内存首地址;
  • src (资)源内存首地址;
  • num 拷贝字节数;
  • 返回值:最开始的dest的首地址

可以看到memcpy与memmove的返回值或者参数都是一样的,其实在一些编译器中,memcpy已经被优化为了memmove;

两者区别:

memmove是momcpy的升级版,memcpy不处理内存重叠时可能引发的问题,出现重叠情况可能会拷贝紊乱出错(src<dest);而memmove处理了内存重叠时可能引发的问题!;

C还保留memcpy的原因是,让之前用过memcpy的代码能正常运行;

memcpye

void * memcpy ( void * dest, const void * src, size_t num );

//

//不处理内存重叠中的特殊情况;
void* my_memcpy( void * dest, const void * src, size_t num )
{
    void* ret = dest;
    while(num--){
        *(char*)dest = *(char*)src;
        dest = (char*)dest + 1;
		src = (char*)src + 1;;
    }
    return ret;
}

int main()
{
	int arr[] = {1,2,3,4,5,6,7,8,9,10};
	
    my_memcpy(arr,arr+5,5*4);//6,7,8,9,10,6,7,8,9,10 //内存重叠,但是src<dest,没问题
    my_memcpy(arr+1,arr,5*4); //1,1,1,1,1,1,7,8,9,10 //内存重叠,但是src>dest,出现问题了,这种src<dest得从后往前拷贝才能达到预期效果!
	
	return 0;
}

内存重叠

如下图,源src和目的dest内存有公共部分!

在这里插入图片描述

上图src>dest时,如果类似memcpy正常从左向右进行拷贝,显然结果是不对的;

这时候后memmove出现了,针对内存重叠情况做出判断,按照特定的方式(前向后 or 后向前)进行拷贝,达到预期效果;

memmove

void * memmove ( void * dest, const void * src, size_t num );



void * my_memmove ( void * dest, const void * src, size_t num )
{
     void* ret = dest;
    //核心就是根据src与desc的大小关系,进行分类操作(正拷贝 or 逆拷贝)
    if(src<dest){
        //src在desc前面!逆拷贝;
        while(num--){
            *( (char*)dest+num) =  *( (char*)src+num);//优雅~
        }
    }
    else{
        //顺拷贝;
         while(num--)
         {
        	*(char*)dest = *(char*)src;
         	dest = (char*)dest + 1;
			src = (char*)src + 1;;
    	}
    }
    return ret;
}

自定义类型(4星)

内存对齐

结构体的大小往往不是结构体中各种数据类型的加和,因为其存在内存对齐;

结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到对齐数整数倍的地址处

对齐数:编译器默认的一个对齐数 **与 该成员大小的较小值 **(VS中默认的值为8 Linux中的默认值为4)

  1. 结构体总大小最大对齐数每个成员变量确定的较小对齐数最大的那个)的整数倍

    (注意,对其书不一定包含VS平台那个8,如果每个成员大小都小于8,那么结构体总大小就是那些成员中最大的类型值得整数倍)

下面是一个对齐后总大小为16的结构体:

在这里插入图片描述

为什么存在内存对齐?

性能原因:

CPU的优化规则与CPU命中率有关,大致原则是这样的:对于n字节的元素(n=2,4,8,…),它的首地址能被n整除,才能获得最好的性能。为了访问未对齐的内存,处理器需要作两次内存访问而对齐的内存访问仅需要一次访问

所以内存对齐本质上是一种空间换时间的优化;(现代内存空间大大的多,更注重时间了);

根据内存对齐的特征,设计结构体时,让较小的成员聚集在一起可以节省空间!

结构体,联合体,枚举

结构体

一个事物具有多重属性或方法,打包成一个结构体,方便操作处理;有点面向对象的意思;

struct People{
	int id;
    char* name;
    //...多重属性
 public:
    int Getid(){//方法1:返回这个人的id
        return id;
    }
    
};

枚举

枚举==列举

enum Day//星期
{
 Mon = 1,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};

enum Sex//性别
{
 MALE,
 FEMALE,
 SECRET
};

{}中的内容是枚举类型的可能取值,也叫 枚举常量

这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。 例如:

枚举的优点(与宏定义常量对比一下):

宏定义的常量不够严谨;

枚举自动递增,方便管理,增加代码的可读性和可维护性;

使用方便,一次可以定义多个常量;

联合体(共用体)

联合的成员是共用同一块内存空间的(有重叠),这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有 能力保存最大的那个成员)。

 union Un
    {
        int i;
        char c;
    };
union Un un;
 un.i = 0x11223344;
 un.c = 0x55;
//下面两条输出结果一样,因为他们共用同一块空间,起始地址都是相同的
 printf("%x\n", &un.i);
 printf("%x\n", &un.c);

 printf("%x\n", un.i);//输出11223355;  因为i和c共同一四字节的空间,第二次c放入0x55将i的44覆盖掉了;
 
	


位段

和结构体很像,与结构体相比,位段更节省空间,但是不具有跨平台性;

位段常用在确定的某些结构,省点空间,eg各种网络报文
在这里插入图片描述

struct S
{
 	char a:3;
 	char b:4;
	char c:5;
 	char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

位段**按照类型(char int等)**开空间,如果这个空间的二进制位没用完,而且能放下下一个成员,那就共用一段空间(内存不能重叠),剩余二进制位置放不下的话,那只好再开辟一个类型空间了;

在这里插入图片描述

编译链接(3星)

我们编写的程序代码是怎样运行起来的?到底运行的是什么内容?那就是编译和链接的全过程

在这里插入图片描述

编译和链接的过程

预编译

.c生成.i文件

也叫预处理,宏替换,去掉注释,添加行号等;

编译

.i生成.s汇编文件

编译是对于预处理完的文件进行一些列的词法分析语法分析语义分析优化后产生相应的汇编代码文件,内联函数替换(一种优化)在这一阶段!

汇编

.s文件转成.o文件

汇编代码转化成机器可以执行的命令,汇编代码转换成机器指令;

前三部分用编译器 后面链接用链接器

链接

静态链接:编译阶段就把静态库就加到可执行文件中去,这样可执行文件就会比较大。

动态链接:在链接阶段仅仅只加入一些描述信息,而程序执行时再从系统中把相应动态库加载到内存中去。

链接程序的主要工作就是将有关的目标文件彼此相连接,库函数代码,我们写的多个代码文件连接起来,合并成一个可执行程序,即可运行!

条件编译

这里的“条件”就是用IF判断是否编译的时候要执行某些操作;

有点像运行时候的if判断程序怎么运行,条件编译是if判断怎么编译;

条件编译可以用于程序DE_BUG调试,也可以防止某个头文件被重复包含;

#ifndef __TEST_H__
#define __TEST_H__
//头文件的内容
#endif //__TEST_H__ d


#pragma once //也可以防止重复包含;

操作符和关键字(4星)

volatile

volatile(易变的)是一个类型修饰符,作用是作为指令关键字,一般都是和const对应,确保本条指令不会被编译器的优化而忽略,使用这个变量时直接读取原始内存地址。

int main()
{
	volatile int i = 10;
	int a = i;

	printf("%d", i);

	//下面 汇编语句 的作用就是改变内存中i的值,但是又 不让编译器知道
	__asm
	{
		mov dword ptr[ebp - 4], 20h
	}
	int b = i;
	printf("i=%d", b);
	return 0;
}

然后,在debug(调试)版本模式运行程序,输出结果如下:

i = 10
i = 32 //修改过后的正确的i值

relese版本下的程序会自动优化,编译器不知道汇编语句改了i的值所以第二次给b赋值i的时候,看之前i没动过,就直接优化把之前i的缓存的10给b了,所以两次结果都是之前的10:

i = 10
i = 10//明明改了值,怎么还是之前的10?错误情况

这显然是有问题的,比如多线程程序改掉共用的一个变量,另个线程不知道,还用的之前的缓存cache,就有问题了;

所以volatile关键字声明这个变量是易变的换句话说,每次用这个变量的时候,都得直接读取原始内存地址,不能用任何之前的cache优化了!

const

提高程序健壮性

修饰普通变量

  1. 与宏定义常量很像;但是宏没类型检查,const更安全;
  2. const可以保护被修饰的东西,防止意外修改增强程序的健壮性
  3. const的常量一般不分配内存,直接放入编译符号表,效率更高;

修饰指针

const int* p; //指向常量的指针, p指向位置的内容不能被修改;

int * const p; //指针常量,p指向的位置不能修改;

const int* const p; //p的位置和位置里的内容都不能被修改;

修饰函数参数

void StringCopy(char *strDestination, const char *strSource指针指向的内容不被改变;);
//保证strSource“源”,指针指向的内容 在函数中 不被改变;

修饰函数返回值

针对返回引用或者指针的函数;

class Student {
public:
   //返回左值引用,可以修改;
    int& GetAge() {
        return m_age;
    }
	
    //返回右值引用,不能修改;
    const int& GetAgeConst() {
        return m_age;
    }

    void ShowAge() {
        cout << "Age: " << m_age << endl;
    }

private:
    int m_age = 0;
};

int main()
{
    Student stu;
    stu.ShowAge();

    stu.GetAge() = 5; // 会修改成员变量的值
    stu.ShowAge();

    stu.GetAgeConst() = 8; // 编译器会报错,因为该返回值被const了 成为了右值;
    stu.ShowAge();

    return 0;
}

修饰成员函数

#include <iostream>
using namespace std;
 
struct A{
private:
	int i;
public:
	void set(int n){ //set函数需要设置i的值,所以不能声明为const
		i = n;
	}
 
	int get() const{ //get函数返回i的值,不需要对i进行修改;
        			//则可以用const修饰。防止在函数体内对i进行修改。						
        			//并且修饰以后,const函数也不能调用其他的非const成员函数,提升程序的健壮性;
		return i;
	}
};

static(结合C++)

提高程序模块性;

修饰局部变量

普通的变量在函数中或者某个作用于用完以后会被释放,下次使用它的时候重新定义;

static修饰的变量就像一个全局变量一样,作用域内用完以后不会被立即释放,和全局变量一样储存在全局区,只会被定义一次;

因为是在某作用于内部,区别于全局变量在main的外部,所以极有利于模块化了;

修饰全局变量

全局变量定义在函数体外部,在全局数据区分配存储空间,且编译器会自动对其初始化。

  • 普通全局变量对整个工程可见,其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。

  • static修饰过的全局变量只对当前文件可见其他文件不能访问,其他文件可以定义相同名字的全局变量,两者没有影响;

定义不需要共享的全局变量时,加上static修饰,那么就能有效降低各程序文件之间的耦合度避免全局变量名冲突;

C++

修饰数据成员

struct S
{
    static int a;
};
int S::a = 10;、
S s;

//S::a or s.f()这样用

当数据成员被static修饰以后,他生命周期就随类本身了,储存在全局数据区,只有这一个,不属于任何该类的实例;

修饰成员函数

struct S
{
    static int f(){ 
        //....
    };
};
int S::a = 10;
S s;

// S::f() or a.f()这样用

类似于修饰数据成员,修饰函数以后,存在全局区; 该函数不属于类的任何实例;

没有this指针!(多线程的handler函数参数就可以匹配了,不然多个this*的参数),也意味着不能访问任何数据成员了,他不属于任何实例!;

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

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

相关文章

循环队列实现---kfifo

循环队列 概述 在优化系统性能时&#xff0c;我们通常需要分析一个单线程程序各模块的功能和性能&#xff0c;然后将这些模块拆分到多个线程中并行执行。而多个线程之间需要加入缓存以实现线程间的通信。如图1所示&#xff1a; 图1&#xff1a;多线程缓存为方便进程间通信&am…

Python【xpath】实战下

项目要求&#xff1a;获取某二手租房平台关于房源信息的简介和价格代码&#xff1a;python编写&#xff0c;实现需要准备的第三方库&#xff1a;requests &#xff0c;lxml&#xff0c; time代码分析&#xff1a;导入需要使用的第三方库&#xff1a;import requests import tim…

java ssm校园快递代领系统的设计与实现idea maven

近几年随着国民经济的不断发展&#xff0c;电子商务行业的不断创新。作为物流业一个重要分支的校园快递代领逐渐兴起&#xff0c;各种快递公司层出不穷。校园快递代领在不断向前发展的同时也存在一些无法避免的小问题&#xff0c;例如许多小型的快递公司在信息处理和管理上存在…

基于微信小程序的民宿短租系统小程序

文末联系获取源码 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏览器…

Spring Boot Actuator详解

Actuator简介 什么是Spring Boot Actuator&#xff1f; Spring Boot Actuator 模块提供了生产级别的功能&#xff0c;比如健康检查&#xff0c;审计&#xff0c;指标收集&#xff0c;HTTP跟踪等&#xff0c;帮助我们监控和管理Spring Boot应用。这个模块是一个采集应用内部信…

react知识点整理

1、react hooks是用来做什么的? 加入hooks,让react函数组件更加的灵活 hooks之前,React存在很多问题: 1、组件间服用状态逻辑难2、复杂组件变的难以理解,高阶组件和函数组件的嵌套过深3、class组件的this问题4、难以记忆的生命周期hooks有: useState()useEffects()useR…

从头开始创建一个OData SAP Gateway Service

可能用到的事务代码&#xff1a;/IWFND/IWF_ACTIVATE – Activate / Deactivate SAP GatewaySEGW – SAP Gateway Service Builder/IWFND/MAINT_SERVICE – Activate and Maintain Services/IWFND/EXPLORER – Service Explorer/IWFND/GW_CLIENT – SAP Gateway Client/IWFND/S…

Android | 输入系统(IMS)

前言 一般情况下很多同学对于点击事件的认识都只存在于从 Activity 开始的&#xff0c;然后从 Window 中进行分发&#xff0c;甚至有些人可能也只知道 onTouchEvent 和 dispatchTouchEvetn 这几个方法&#xff0c;对于 View 层的了解都不属性。 自从对于应用层面的分发过程了…

logging日志模块详解

说到日志&#xff0c;无论是写框架代码还是业务代码都离不开日志的记录&#xff0c;其能给我们定位问题带来极大的帮助。 记录日志最简单的方式是在你想要记录的地方加上一句print。我相信无论是新手还是老鸟都经常这么干&#xff0c;在简单的代码或者小型项目中这么干一点问题…

这样吃才能有效补脑

核桃长得像大脑&#xff0c;还含有Ω-3&#xff0c;一直被认为补脑效果很好。但是现代科学研究发现&#xff0c;Ω-3并不是核桃专有的&#xff0c;很多坚果都有&#xff0c;所以核桃在补脑这方面并没有什么特殊功效。补脑其实就是维持大脑的正常工作&#xff0c;还要延缓大脑认…

SAP ADM100-Unit4 数据库工作原理:监控数据库

概览 除了执行数据备份之外,还需要对数据库进行大量的周期性检查。 课程目标 对数据库计划额外的周期性检查。 1、数据库定期监控 除了日常监控数据库备份外,还有大量的其他检查不得不定期执行。有的检查可以通过DBA Cockpit Planning Calendar来计划。 例如: 当存取数…

Mac电脑使用:查看本机已连接Wi-Fi密码的方法

前言 在使用Mac电脑的时候&#xff0c;电脑自身所连接成功的Wi-Fi一般都不显示密码&#xff0c;这是苹果出于安全考量的保护措施&#xff0c;但是有时候遇到新的设备想要连接已经连过的Wi-Fi&#xff0c;由于时间太久忘记Wi-Fi密码&#xff0c;这就需要查看一下电脑连接的Wi-Fi…

点击化学标记1817735-33-3,Pyrene-PEG5-propargyl,芘甲酰胺五聚乙二醇丙炔

Pyrene-PEG5-propargyl&#xff0c;芘甲酰胺-五聚乙二醇-丙炔Product structure&#xff1a;Pyrene-PEG5-propargyl结构式Product specifications&#xff1a;1.CAS No&#xff1a;1817735-33-32.Molecular formula&#xff1a;C30H33NO63.Molecular weight&#xff1a;503.64.…

终于有多位大神联手把计算机基础知识与操作系统讲清楚了

操作系统的定义 指的是控制和管理整个计算机系统的硬件和软件资源&#xff0c;并合理地组织调度计算机的工作和资源的分配&#xff0c;以提供给用户和其他软件方便的接口和环境&#xff0c;它是计算机系统中最基本的系统软件。 计算机系统的层级结构 1、用户 应用程序 2、操…

vue前端框架课程笔记(三)

目录条件渲染v-ifv-show列表渲染关于:key列表过滤watch属性实现computed属性列表排序表单数据收集input是否配置value属性过滤器本博客参考尚硅谷官方课程&#xff0c;详细请参考 【尚硅谷bilibili官方】 本博客以vue2作为学习目标&#xff08;请勿混淆v2与v3的代码规范&…

Azure 语音用人工智能改变游戏开发的三种方式

通过 Azure 认知服务的智能语音功能[1]&#xff0c;用户可以使用语音 SDK 开发工具包快速构建支持语音交互的各种应用。将语音转录为准确的文本 &#xff08;STT&#xff0c;或语音识别&#xff09;或者将文本转换成生动的语音 &#xff08;TTS&#xff0c;或语言合成&#xff…

大数据NiFi(十五):NiFi入门案例二

文章目录 NiFi入门案例二 一、配置“GenerateFlowFile”处理器 1、拖拽“Processor”在弹框中输入“GenerateFlowFile” <

如何解决缓存雪崩、击穿、穿透难题?

缓存雪崩、缓存击穿和缓存穿透这三个问题是我们在使用redis做缓存的时候要面临的&#xff0c;一旦发生这三个问题&#xff0c;就会导致大量的请求都积压到了数据库层&#xff0c;有可能导致数据库宕机&#xff0c;进入导致整个系统不可用。 下边&#xff0c;具体看一下这三个问…

Qt扫盲-Qt资源系统概述

Qt资源系统概述一、概述二、资源文件(.qrc)三、外部二进制资源四、内嵌资源五、压缩资源文件六、在应用中使用资源七、使用Library 库中的资源一、概述 Qt资源系统是一种独立于平台的机制&#xff0c;用于在应用程序的可执行文件中存储二进制文件。如果您的应用程序总是需要一…

Spring Boot学习之集成Dubbo+Zookeeper小案例

文章目录一 框架搭建1. [Dubbozookeeper下载和安装](https://blog.csdn.net/yang2330648064/article/details/128790320)二 项目创建2.1 服务者部分2.2 消费者部分2.3 注意点2.3.1 在service的实现类中配置服务注解&#xff0c;发布服务&#xff01;注意导包问2.3.2 服务接口的…