深入分析 Spring 基于注解的 AOP 实现原理

article/2025/10/3 13:13:08

一、AOP 的基本使用

AOP 的使用分为三步走:

  1. 将业务逻辑组件和切面类都加入到容器中:告诉 Spring 哪个是切面类;@Aspect
  2. 在切入类上的每一个通知方法上标注通知注解:告诉 Spring 何时何地运行(切入点表达式)@Pointcut@Before~~~
  3. 在配置类上开启基于注解的 AOP 模式;@EnableAspectJAutoProxy

使用 aop 相关的注解必须先导入依赖:

<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.1.2.RELEASE</version>
</dependency>

下面以一个计算器的例子来介绍 AOP 的基本使用:

1、待增强类

这是一个简单的计算器类,为了能够演示异常,所以创建了一个有除法的方法。

public class MathCalculator {/*** 除法** @param i 被除数* @param j 除数* @return 返回运算结果*/public int div(int i, int j) {return i / j;}
}

2、增强类

我们想通过 AOP 实现记录除法运行的日志信息,所以新建一个 Log 类。

@Aspect
public class LogAspect {/*** 抽取出来的切入点表达式*/@Pointcut("execution(* top.wsuo.aop.MathCalculator.*(..))")public void pointCut() {}/*** 前置通知** @param joinPoint 连接点*/@Before("pointCut()")public void logStart(JoinPoint joinPoint) {System.out.println(joinPoint.getSignature().getName() + "运行...参数列表是:{" + Arrays.toString(joinPoint.getArgs()) + "}");}/*** 后置通知** @param joinPoint 连接点*/@After("pointCut()")public void logEnd(JoinPoint joinPoint) {System.out.println(joinPoint.getSignature().getName() + "结束...");}/*** 返回通知** @param joinPoint 连接点* @param result    执行结果*/@AfterReturning(value = "pointCut()", returning = "result")public void logReturn(JoinPoint joinPoint, Object result) {System.out.println(joinPoint.getSignature().getName() + "正常返回...运行结果:{" + result + "}");}/*** 异常通知** @param joinPoint 连接点* @param exception 异常信息*/@AfterThrowing(value = "pointCut()", throwing = "exception")public void logException(JoinPoint joinPoint, Exception exception) {System.out.println(joinPoint.getSignature().getName() + "出现异常...异常信息:{" + exception.getMessage() + "}");}
}

3、配置类

最后在配置类上开启注解版 AOP,同时注册组件到容器中。

@Configuration
// Spring 中有很多 EnableXXX 代表开启某一项功能: 取代了配置
@EnableAspectJAutoProxy
public class MainConfigOfAOP {@Beanpublic MathCalculator mathCalculator() {return new MathCalculator();}@Beanpublic LogAspect logAspect() {return new LogAspect();}
}

4、测试

测试及运行结果。

@Test
public void test12() {ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);MathCalculator calculator = context.getBean(MathCalculator.class);System.out.println(calculator.div(4, 2));
}

二、注解 AOP 的实现原理

1、@EnableAspectJAutoProxy

整个 AOP 要想起作用,必须加上 @EnableAspectJAutoProxy 注解,这个注解的作用是什么呢?

点进去该注解:

@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {

看到了要导入一个 AspectJAutoProxyRegistrar 类组件,它继承自一个接口 ImportBeanDefinitionRegistrar,这个接口我们之前讲过,他是添加自定义组件的接口,在这里:https://blog.csdn.net/weixin_43941364/article/details/107243459。

这说明 @EnableAspectJAutoProxy 注解的作用就是给容器中添加组件, 追踪 AspectJAutoProxyRegistrar 类的方法,发现有这么一段代码:

这段代码的作用就是先看一下容器中有没有

public static final String AUTO_PROXY_CREATOR_BEAN_NAME ="org.springframework.aop.config.internalAutoProxyCreator";

internalAutoProxyCreator 这个类,同时我们看到在调用上述方法的时候,传入了一个类型:

该类型是 AspectJAwareAdvisorAutoProxyCreator 实体类,看一下该类的继承结构。

2、AspectJAwareAdvisorAutoProxyCreator


可以看到该类实现了一个接口,就是 BeanPostProcessor 接口,他是一个 后置处理器 。这个接口是 Bean 生命周期相关的接口。

所以我们要重点分析一下该类的执行顺序,接下来 打断点调试 之前举的计算器的例子。


3、容器的创建流程

从容器启动开始分析:

@Test
public void test12() {ApplicationContext context = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);MathCalculator calculator = context.getBean(MathCalculator.class);System.out.println(calculator.div(4, 2));
}

首先传入配置类,创建 IOC 容器;然后注册配置类,调用 refresh 方法刷新容器;

3.1、注册后置处理器

使用 registerBeanPostProcessors(beanFactory) 注册 Bean 的后置处理器,来拦截 Bean 的创建

  1. 先获取 IOC 容器中已经定义了的需要创建对象的所有 BeanPostProcessor

  2. 给容器中加别的 BeanPostProcessor

  3. 优先注册实现了 PriorityOrdered 接口的 BeanPostProcessor

  4. 再注册实现了 Ordered 接口的 BeanPostProcessor

  5. 之后注册没实现 Ordered 接口的 BeanPostProcessor

  6. 最后 registerBeanPostProcessors 执行,注册 BeanPostProcessor ,实际上就是创建 BeanPostProcessor 对象,保存在容器中。

    创建 org.springframework.aop.config.internalAutoProxyCreatorBeanPostProcessor,它的类型是 AnnotationAwareAspectJAutoProxyCreator

    1. 首先创建 Bean 的实例 instanceWrapper = createBeanInstance(beanName, mbd, args)

    2. 然后给属性赋值 populateBean(beanName, mbd, instanceWrapper)

    3. 最后初始化 Bean exposedObject = initializeBean(beanName, exposedObject, mbd)

      • invokeAwareMethods(beanName, bean) 初始化 Aware 接口的方法回调;
      • applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName) 执行后置处理器的 postProcessBeforeInitialization 方法;
      • invokeInitMethods(beanName, wrappedBean, mbd) 执行自定义初始化方法;
      • applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName) 执行后置处理器的 postProcessAfterInitialization 方法;
    4. 到此为止 AnnotationAwareAspectJAutoProxyCreator 类型的 BeanPostProcessor 创建成功;

  7. 创建完 BeanPostProcessor 对象之后,注册到 beanFactory

    registerBeanPostProcessors(beanFactory, internalPostProcessors)注册方法的实现:for (BeanPostProcessor postProcessor : postProcessors) {beanFactory.addBeanPostProcessor(postProcessor);
    }
    

到此为止 AnnotationAwareAspectJAutoProxyCreator 就算是创建成功了,而它作为一个后置处理器,肯定有作用,下面分析一下他作为后置处理器做了什么事情。

注意 AnnotationAwareAspectJAutoProxyCreatorInstantiationAwareBeanPostProcessor 类型的后置处理器。

3.2、初始化剩下的单实例 Bean

finishBeanFactoryInitialization(beanFactory) 完成 BeanFactory 的初始化工作

  1. 遍历获取容器中所有的 Bean,依次创建对象:getBean、doGetBean、getSingleton
  2. 创建 Bean(业务逻辑组件和切面组件);
    • 先从缓存中获取当前 Bean,如果能获取到,说明 Bean 是之前创建过的,直接使用,否则再创建;

      先从缓存中检查有没有这个 Bean
      // Eagerly check singleton cache for manually registered singletons.
      Object sharedInstance = getSingleton(beanName);如果	他等于 null,才会继续执行下面的方法
      sharedInstance = getSingleton(beanName, () -> {
      

      只要创建好的 Bean 都会被缓存起来,这也是 Spring 保证单实例 Bean 的实现原理。

    • createBean 创建 Bean:AnnotationAwareAspectJAutoProxyCreator 会在任何 Bean 创建完成之前先尝试返回 Bean 的实例,其实就是拦截;

      BeanPostProcessor 是在对象创建Bean完成初始化前后调用的,而 InstantiationAwareBeanPostProcessor 是在创建Bean实例之前先尝试用后置处理器返回对象的。

      • Object bean = resolveBeforeInstantiation(beanName, mbdToUse),这句话的意思是希望后置处理器返回一个代理对象,如果能返回代理对象就使用,如果不能就继续;
        这个方法的实现就是拿到所有后置处理器,如果是 InstantiationAwareBeanPostProcessor,就执行 postProcessBeforeInstantiation 方法
        bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);if (bean != null) {bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
        }
        
      • Object beanInstance = doCreateBean(beanName, mbdToUse, args),真正的去创建一个 Bean,和之前 3.6 的流程是一样的。

所以AnnotationAwareAspectJAutoProxyCreator 会在任何 Bean 创建完成之前先尝试返回 Bean 的实例,因为他实现了 InstantiationAwareBeanPostProcessor 接口,这个接口有两个方法,一个是postProcessBeforeInstantiation,另一个是postProcessAfterInstantiation,这两个方法是在 Bean 创建完成前后执行的,而 BeanPostProcessor 接口的两个方法是在创建完成并且初始化前后调用的。

  1. 在每一个 Bean 创建之前调用 postProcessBeforeInstantiation方法,在这一步找出需要增强的 Bean;

    • 判断当前 Bean 是否在 advisedBeans 中(它保存了所有需要增强的 Bean )

    • 判断当前 Bean 是否是基础类型 isInfrastructureClass或者是切面。

    • 判断是否该跳过 shouldSkip:源码如下

      @Override
      protected boolean shouldSkip(Class<?> beanClass, String beanName) {// TODO: Consider optimization by caching the list of the aspect namesList<Advisor> candidateAdvisors = findCandidateAdvisors();for (Advisor advisor : candidateAdvisors) {if (advisor instanceof AspectJPointcutAdvisor &&((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {return true;}}return super.shouldSkip(beanClass, beanName);
      }
      

      首先获取所有候选的增强器,增强器就是切面里面的通知方法;

      0 = {InstantiationModelAwarePointcutAdvisorImpl@2180} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void top.wsuo.aop.LogAspect.logStart(org.aspectj.lang.JoinPoint)]; perClauseKind=SINGLETON"
      1 = {InstantiationModelAwarePointcutAdvisorImpl@2181} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void top.wsuo.aop.LogAspect.logEnd(org.aspectj.lang.JoinPoint)]; perClauseKind=SINGLETON"
      2 = {InstantiationModelAwarePointcutAdvisorImpl@2182} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void top.wsuo.aop.LogAspect.logReturn(org.aspectj.lang.JoinPoint,java.lang.Object)]; perClauseKind=SINGLETON"
      3 = {InstantiationModelAwarePointcutAdvisorImpl@2183} "InstantiationModelAwarePointcutAdvisor: expression [pointCut()]; advice method [public void top.wsuo.aop.LogAspect.logException(org.aspectj.lang.JoinPoint,java.lang.Exception)]; perClauseKind=SINGLETON"
      

      可以看到这就是我们的那几个通知方法。

      只不过他把这些通知方法包装成为了一个 List<Advisor> candidateAdvisors 集合,每一个封装的通知方法的增强器是 InstantiationModelAwarePointcutAdvisor
      这段代码的逻辑就是判断每一个增强器是否是 AspectJPointcutAdvisor 类型的,如果是返回 true ,如果不是就返回 false

  2. 在 Bean 创建之后调用 postProcessAfterInitialization 方法,在这一步增强需要增强的 Bean:

    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {if (bean != null) {Object cacheKey = getCacheKey(bean.getClass(), beanName);if (!this.earlyProxyReferences.contains(cacheKey)) {return wrapIfNecessary(bean, beanName, cacheKey);}}return bean;
    }
    
    • wrapIfNecessary 方法中,获取当前 Bean 的所有增强器(通知方法),判断是否需要包装(增强)。

      // Create proxy if we have advice.
      Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
      
      @Nullable
      protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
      }
      

      那么他是 怎么找的增强器 呢 ?我们继续查看方法调用。

      protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {List<Advisor> candidateAdvisors = findCandidateAdvisors();List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
      }
      

      这一步是找到候选的所有增强器,即哪些通知方法是需要切入当前 Bean 方法的。

      然后下面的方法 findAdvisorsThatCanApply 是获取到能在当前 Bean 使用的增强器,它使用了 canApply 方法判断。

      public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {if (advisor instanceof IntroductionAdvisor) {return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);}else if (advisor instanceof PointcutAdvisor) {PointcutAdvisor pca = (PointcutAdvisor) advisor;return canApply(pca.getPointcut(), targetClass, hasIntroductions);}else {// It doesn't have a pointcut so we assume it applies.return true;}
      }
      

      最后是给这些增强器排序。

    1. 保存当前 Bean 到 advisedBeans 中;
    2. 如果当前 Bean 需要增强,创建当前 Bean 的代理对象;
      • 获取所有的增强器(通知方法)

      • 保存到 proxyFactory 中;

        proxyFactory.getProxy(getProxyClassLoader());
        
      • 创建代理对象,Spring 自动决定使用哪一种动态代理

        @Override
        public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}
        }
        

        可以看到有两种自动代理,分别是 JdkDynamicAopProxyObjenesisCglibAopProxy

        此时创建完成之后,代理对象为 top.wsuo.aop.MathCalculator,通过 Spring 增强的类型。

    4.所以最后 wrapIfNecessary(bean, beanName, cacheKey) 方法就是返回了当前组件使用的 cglib 增强了的代理对象。

    5.以后容器中获取到的就是这个组件的 代理对象 ,执行目标方法的时候,代理对象就会执行通知方法的流程。

3.3、执行目标方法

我们在测试方法上面打断点,看看除法运行的时候都有啥:

观察到此时的对象已经是 cglib 代理之后的对象了,这个对象中保存了详细信息,比如所有的增强器和目标对象。

  1. 下面进入到 org.springframework.aop.framework.CglibAopProxy.DynamicAdvisedInterceptorintercept 方法中。

    本来是想执行目标的,但是代理之后就要先被拦截一下。

  2. 然后根据 ProxyFactory 对象获取将要执行的目标拦截器链;

    List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)
    

    拦截器链是如何获取的?

    主要是在 getInterceptorsAndDynamicInterceptionAdvice 方法中。

    • 首先创建一个集合保存所有的拦截器,默认有 5 个
      List<Object> interceptorList = new ArrayList<>(advisors.length);
      
      这 5 个包括一个默认的 org.springframework.aop.interceptor.ExposeInvocationInterceptor.ADVISOR 和 4 个增强器。
    • 遍历所有的增强器,将其转为 Interceptor
      for (Advisor advisor : advisors)registry.getInterceptors(advisor)
      
    • 将增强器转为 List<MethodInterceptor>
      • 如果本来就是 MethodInterceptor,则直接加到集合中;

      • 如果不是,则使用 AdvisorAdapter 适配器转为 MethodInterceptor

        怎么转的呢,其实这里就是强转然后包装了一下,源码如下。

        @Override
        public MethodInterceptor getInterceptor(Advisor advisor) {AfterReturningAdvice advice = (AfterReturningAdvice) advisor.getAdvice();return new AfterReturningAdviceInterceptor(advice);
        }
        

        可以看到这就是 最终通知

      • 转化完成返回 MethodInterceptor 数组。

    • 所以 拦截器链 就是每一个通知方法又被包装成为方法拦截器,利用 MethodInterceptor 的机制控制执行顺序。
  3. 如果没有拦截器链,直接执行目标方法

    retVal = methodProxy.invoke(target, argsToUse);
    
  4. 如果有拦截器链,把需要执行的目标对象,目标方法,拦截器链等信息传入创建一个 CglibMethodInvocation 对象,并调用它的 proceed 方法。

    // We need to create a method invocation...
    retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
    
  5. 拦截器链的触发过程,触发方法就是 proceed,所以只需要分析一下这个方法即可。

    • 如果没有拦截器或者是最后一个拦截器就执行目标方法
      if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {return invokeJoinpoint();
      }
      
    • 如果有拦截器就链式的获取每一个拦截器,拦截器执行 invoke 方法,每一个拦截器等待下一个拦截器执行完成返回以后再来执行。
      这里的返回值是还是拦截器,传入的是这个拦截器本身,每次调用都会减少一个长度,并且改变当前的拦截器,所以执行顺序是栈式的结构。
      return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
      
      • 首先执行到 interceptorOrInterceptionAdvice 的实现类 ExposeInvocationInterceptor,就是方法本身;

        跟进去执行的是 org.springframework.aop.interceptor.ExposeInvocationInterceptorinvoke 方法,该方法的实现如下:

        @Override
        public Object invoke(MethodInvocation mi) throws Throwable {MethodInvocation oldInvocation = invocation.get();invocation.set(mi);try {return mi.proceed();}finally {invocation.set(oldInvocation);}
        }
        

        这一块代码的核心业务是放在 finally 中的,所以肯定会执行,下面接着跟进去 proceed 方法:

      • 这个时候再次来到 proceed 方法,此时的下标变为 0,执行到 AspectJAfterThrowingAdvice,即异常通知;

        跟进去执行 org.springframework.aop.aspectj.AspectJAfterThrowingAdviceinvoke 方法,该方法的实现如下:

        @Override
        public Object invoke(MethodInvocation mi) throws Throwable {try {return mi.proceed();}catch (Throwable ex) {if (shouldInvokeOnThrowing(ex)) {invokeAdviceMethod(getJoinPointMatch(), null, ex);}throw ex;}
        }
        

        注意到这里有异常的捕捉,所以异常发生时是在这里处理的,没有异常则不会执行,继续跟进 proceed 方法。

      • 这个时候再次来到 proceed 方法,此时的下标变为 1,执行到 AfterReturningAdviceInterceptor,即返回(最终)通知;

        跟进去执行 org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptorinvoke 方法,该方法的实现如下:

        @Override
        public Object invoke(MethodInvocation mi) throws Throwable {Object retVal = mi.proceed();this.advice.afterReturning(retVal, mi.getMethod(), mi.getArguments(), mi.getThis());return retVal;
        }
        

        就是先执行其他的,然后执行返回通知的内容,继续跟进 proceed 方法。

      • 这个时候再次来到 proceed 方法,此时的下标变为 2,执行到 AspectJAfterAdvice,即后置通知;

        跟进去执行 org.springframework.aop.aspectj.AspectJAfterAdviceinvoke 方法,该方法的实现如下:

        @Override
        public Object invoke(MethodInvocation mi) throws Throwable {try {return mi.proceed();}finally {invokeAdviceMethod(getJoinPointMatch(), null, null);}
        }
        

        就是先执行后面的前置通知,然后执行后置通知的内容,继续跟进 proceed 方法。

      • 这个时候再次来到 proceed 方法,此时的下标变为 3,执行到 MethodBeforeAdviceInterceptor,即前置通知;

        跟进去执行 org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptorinvoke 方法,该方法的实现如下:

        @Override
        public Object invoke(MethodInvocation mi) throws Throwable {this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis());return mi.proceed();
        }
        

        这块代码是先执行自己的业务,再往下传递,我们继续跟进 proceed 方法:

        这个时候再次来到 proceed 方法,此时的下标变为 4,还是执行 MethodBeforeAdviceInterceptor

        但是现在已经开始回溯了,因为方法都已经入栈了,此时执行 前置通知 中的方法,控制台输出如下:

        然后执行 后置通知

        执行 返回通知

        最后所有的通知执行完毕,由于没有异常产生,所以没有执行异常通知:


http://chatgpt.dhexx.cn/article/c6JkUnYt.shtml

相关文章

灵魂画手图解Spring AOP实现原理!

本篇旨在让读者对Spring AOP实现原理有一个宏观上的认识&#xff0c;因此会丢失一些细节&#xff0c;具体实现参考&#xff1a;老实人Spring源码目录 阅读本篇文章前&#xff0c;希望读者对Spring Ioc以及Spring AOP的使用(Aspect)由一定了解&#xff0c;话不多说&#xff0c;直…

Spring AOP实现原理

1、Spring AOP Spring AOP的面向切面编程&#xff0c;是面向对象编程的一种补充&#xff0c;用于处理系统中分布的各个模块的横切关注点&#xff0c;比如说事务管理、日志、缓存等。它是使用动态代理实现的&#xff0c;在内存中临时为方法生成一个AOP对象&#xff0c;这个对象…

AOP实现原理详解

&#xfeff;&#xfeff; 转载地址&#xff1a;https://my.oschina.net/elain/blog/382494 一、什么是 AOP AOP&#xff08;Aspect-OrientedProgramming&#xff0c;面向切面编程&#xff09;&#xff0c;可以说是OOP&#xff08;Object-Oriented Programing&#xff0c;面向…

Spring框架的AOP实现原理

一、AOP的基本概念 AOP先是一种思想&#xff0c;后是一种技术。 AOP&#xff1a;面向切面编程&#xff0c;是将那些与业务无关&#xff08;比如有事务处理&#xff0c;日志管理&#xff0c;权限控制等&#xff09;&#xff0c;但要为业务模块共同调用的逻辑封装成一个可重用的…

Java:由浅入深揭开 AOP 实现原理

概述&#xff1a; 最近在开发中遇到了一个刚好可以用AOP实现的例子&#xff0c;就顺便研究了AOP的实现原理&#xff0c;把学习到的东西进行一个总结。文章中用到的编程语言为kotlin&#xff0c;需要的可以在IDEA中直接转为java。 这篇文章将会按照如下目录展开&#xff1a; A…

Spring的AOP原理

为什么80%的码农都做不了架构师&#xff1f;>>> 一、什么是 AOP AOP&#xff08;Aspect-OrientedProgramming&#xff0c;面向切面编程&#xff09;&#xff0c;可以说是OOP&#xff08;Object-Oriented Programing&#xff0c;面向对象编程&#xff09;的补充和完…

利用C语言实现动态数组

数组存在的问题&#xff1a;如果我们定义一个数组去存储数据&#xff0c;需要提前定义数组的个数&#xff0c;或者数组根据第一次存储的元素个数自动确定数组的大小&#xff0c;但是我们如果想对数组进行元素插入只能重新定义一个新数组&#xff0c;或者预定义一个空间非常大的…

C++数组之动态数组

目录 1.摘要 2.动态数组内存分配 1一维数组 2多维数组&#xff08;以2维为例&#xff09; 3.动态数组初始化 1默认初始化 2.自定义初始化 4.动态数组释放 5.例子 Gradebook类的实现 6.参考文章 1.摘要 数组是一种顺序存储的数据结构&#xff0c;在定义数组时&…

C++中如何定义动态数组

首先&#xff1a;为什么需要动态定义数组呢&#xff1f; 这是因为&#xff0c;很多情况下&#xff0c;在预编译过程阶段&#xff0c;数组的长度是不能预先知道的&#xff0c;必须在程序运行时动态的给出 但是问题是&#xff0c;c要求定义数组时&#xff0c;必须明确给定数组…

c++ 动态数组

动态数组 相关数组知识连接 数组详解 多维数组 在之前的文章中&#xff0c;讲解了数组的相关知识&#xff0c;那一种数组&#xff08;数组相关连接&#xff1a;https://blog.csdn.net/m0_62870588/article/details/123787052&#xff09;又称是静态数组&#xff0c;因为它的大小…

C语言中动态分配数组

很多人在编写C语言代码的时候很少使用动态数组&#xff0c;不管什么情况下通通使用静态数组的方法来解决&#xff0c;在当初学习C语言的时候我就是一个典型的例子&#xff0c;但是现在发现这是一个相当不好的习惯&#xff0c;甚至可能导致编写的程序出现一些致命的错误。尤其对…

C语言学习笔记:动态数组

动态数组 数组是C语言中的很重要的一种构造类型&#xff0c;最初我们学习的都是静态数组&#xff0c;但是&#xff0c;静态数组有着自己难以改变的缺点——数组长度固定。 一般在静态数组定义后&#xff0c;系统就会为其分配对应长度的连续的专有内存空间&#xff0c;可是&am…

C语言如何实现动态数组?

提出问题 请问在c语言里如何实现动态大小的数组啊&#xff0c;比如说int a[N];&#xff0c;这里N的值可以在程序中定&#xff0c;或者有什么方法可以实现类似的功能&#xff1f;总之只要在编译时不用制定数组大小就行。 分析问题 嵌入式系统的内存是宝贵的&#xff0c;内存是否…

C的动态数组的详细知识(网上收集到的大量详细知识以及个人理解的汇总)

动态数组是指在声明时没有确定数组大小的数组&#xff0c;即忽略圆括号中的下标&#xff1b;当要用它时&#xff0c;可随时用ReDim语句重新指出数组的大小。使用动态数组的优点是可以根据用户需要&#xff0c;有效利用存储空间。 可以了解动态数组的详细定义 一.C版本动态数组…

动态数组C语言实现详解

目录 0、前言 一、动态数组数据结构 二、动态数组增删改查函数声明 三、数组创建 1、头部动态创建 2、头部静态创建 四、元素添加 五、元素删除 1、根据元素值删除 2、根据元素位置删除 六、元素修改 七、元素查找 八、数组清空 九、数组销毁 十、验证程序 0、前…

C语言实现 动态数组 处理任意类型数据

引言&#xff1a;动态数组在C/C、Java、Python等语言中应用广泛&#xff0c;高级语言一般通过调用类或接口等可以快捷使用&#xff0c;C语言实现动态数组需要手动构造&#xff0c;以下为实现过程。 1 结构体构造动态数组 typedef struct Array {void **p; //维护在堆区…

C语言创建动态数组

C语言创建动态数组 1.编写步骤 1. 添加所需头文件 stdlib.h 该头文件下包含的与分配存储区相关的函数如下&#xff1a; void* malloc (size_t size);//从堆中分配size字节的存储空间 void* calloc (size_t num, size_t size);//分配数组并将数组零初始化。为 num 个元素的数…

在OpenCV里实现开运算

前面学习腐蚀和膨胀算法,并且深刻地认识到它们的特性以及作用。如果由这两种组合出来的运算又有什么样的不同呢?比如一个图像先腐蚀后膨胀的操作,会有什么结果呢?因为腐蚀是把图片白色变小,膨胀又是把图片白色变大,是否会保持原图不变呢?带着这些问题来研究一下先腐蚀后…

OpenCV python 形态学 圆形开运算

处理流程 # -*- coding: utf-8 -*- # note : 形态学 开运算 圆形内核 处理 # --------------------------------import cv2 as cv import numpy as npdef opening_circle(img_bin, kernel_size10):# 形态学kernel np.zeros((kernel_size, kernel_size), np.uint8)center_…

腐蚀、膨胀、开运算、闭运算

一、腐蚀、膨胀、开运算、闭运算 腐蚀&#xff1a;图像中的高亮部分进行膨胀 膨胀&#xff1a;原图中的高亮部分被腐蚀&#xff0c;类似于领域被蚕食 开运算&#xff1a;先腐蚀再膨胀&#xff0c;可以去掉目标外孤立的点 闭运算&#xff1a;先膨胀再腐蚀&#xff0c;可以去掉目…