2024年11月

1. 前言

本篇我们讲解
2个月搞定计算机二级C语言
——真题7

真题7-程序评分

2. 程序填空题

2.1 题目要求

真题7-程序填空

2.2 提供的代码

#include <stdio.h>
int fun(char* s, char* t) {
    int n = 0;
    while (*s) {
        if (*s < 97) {
    /**********found**********/
            *(t + n) = __1__;
            n++;
        }
    /**********found**********/
        __2__;
    }
    *(t + n) = 0;
    /**********found**********/
    return __3__;
}
main() {
    char s[81], t[81];
    int  n;
    printf("\nEnter a string:\n");
    gets(s);
    n = fun(s, t);
    printf("\nThere are %d letter which ASCII code is less than 97: %s\n", n, t);
    getchar();
}

2.3 解题思路

题目要求判断形参
s
所指字符串的所有字符,所以我们需要遍历这个字符串,依次判断当前字符是否小于 97,是则存入形参
t
所指的字符数组中,同时计数变量
n
加一。

第(1)处填空:

这里在
if
的内部,说明
*s
是小于 97 的,所以我们要把当前
*s
中的元素赋值给
*(t + n)
,从而形成新的字符串。

这里为什么是赋值给
*(t + n)
呢?

因为形参
t
只传入了它指向的字符串的首地址(即
main
函数中
char t[81]
的首地址-->
t[0]
),同时
n
作为计数的变量正好可以作为指针向后移动的单位。

// 第一次进入 n = 0,*(t + 0) = *s
// 第二次进入 n = 1,*(t + 1) = *s
// 第三次进入 n = 2,*(t + 2) = *s
// 下同
*(t + n) = *s;

第(2)处填空:

我们需要遍历形参
s
才能判断所有的字符,所以这里使用
*s++;
将指针
s
的地址后移一位,直到执行到
*s
中的元素为
'\0'
时表示字符串结束,则循环结束。

*s++;

第(3)处填空:

n
作为计数的遍历,这里需要作为函数值返回。

return n;

2.4 代码实现

填写完整的代码:

#include <stdio.h>
int fun(char* s, char* t) {
    int n = 0;
    while (*s) {
        if (*s < 97) {
    /**********found**********/
            *(t + n) = *s;
            n++;
        }
    /**********found**********/
        *s++;
    }
    *(t + n) = 0;
    /**********found**********/
    return n;
}
main() {
    char s[81], t[81];
    int  n;
    printf("\nEnter a string:\n");
    gets(s);
    n = fun(s, t);
    printf("\nThere are %d letter which ASCII code is less than 97: %s\n", n, t);
    getchar();
}

提示:为确保代码正常运行,请在题库编程环境的对应题目中进行测试和运行。

3. 程序修改题

3.1 题目要求

真题7-程序修改

3.2 提供的代码

#include <conio.h>
#include <stdio.h>
#define M 10
int a[M][M] = {0};

/**************found**************/
void fun(int** a, int m) {
    int j, k;
    for (j = 0; j < m; j++)
        for (k = 0; k < m; k++)
/**************found**************/
            a[j][k] = k * j;
}

main() {
    int i, j, n;

    printf(" Enter n : ");
    scanf("%d", &n);
    fun(a, n);
    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++)
            printf("%4d", a[i][j]);
        printf("\n");
    }
    getchar();
}

3.3 解题思路

这个我们直接看修改的地方。

第(1)处修改:

我们在打开程序时可以看到
main
函数中
fun(a,n);
这条语句的
a
下面有条红色波浪线,把鼠标移上去会显示实参与形参不兼容,那么第一处修改应该从
fun
函数的形参
a
动手。

实参是全局变量的二维数组
a[M][M]
,所以我们需要将形参改为
a[][M]
,只需要指定二维数组列的大小即可。

int **a
表示一个指向指针的指针,即它指向一个指针数组,其中每个指针又可以指向一个
int
类型的元素或其他一维数组,它不能保证二维数组的行和列是连续存储的,所以通常用于动态分配的二维数组,需要使用
malloc
为每一行分配独立的内存。


int [][M]
表示一个二维数组的形参,其中列数必须固定(M为10),行数可以不固定,它在内存中是连续存储的。

void fun(int a[][M], int m) {

第(2)处修改:

修改完 1 处后,程序可以运行了,但结果是不正确的,会输出好多 0。

其实不难看出,程序的
for

j、k
是从 0 开始的,其原因是为了将数据存储到
a[0][0]
中,但下面的计算又将
j
直接和
k
相乘,从而导致好多数都是 0。

所以我们需要将程序修改为下面的表达式,这样
a[j][k]
就会存储
行索引+1

列索引+1
的乘积,这里注意不要使用
j++

k++
,因为它们等价与
j = j + 1

k = k + 1
,同意也会导致输出错误。

a[j][k] = (j + 1) * (k + 1);

3.4 代码实现

修改后的代码:

#include <conio.h>
#include <stdio.h>
#define M 10
int a[M][M] = {0};

/**************found**************/
void fun(int a[][M], int m) {
    int j, k;
    for (j = 0; j < m; j++)
        for (k = 0; k < m; k++)
/**************found**************/
            a[j][k] = (j + 1) * (k + 1);
}

main() {
    int i, j, n;

    printf(" Enter n : ");
    scanf("%d", &n);
    fun(a, n);
    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++)
            printf("%4d", a[i][j]);
        printf("\n");
    }
    getchar();
}

提示:为确保代码正常运行,请在题库编程环境的对应题目中进行测试和运行。

4. 程序设计题

4.1 题目要求

真题7-程序设计

4.2 提供的代码

#include <stdio.h>
void NONO();
void fun(int* a, int* b, int* c, int* d) {
}
main() {
    int a, b, c, d;
    printf("请输入4个整数:    ");
    scanf("%d%d%d%d", &a, &b, &c, &d);
    printf("输入数据:    %d,%d,%d,%d\n", a, b, c, d);
    fun(&a, &b, &c, &d);
    printf("最大值:%d,最小值:%d\n", a, d);
    NONO();
    getchar();
}

void NONO() { /* 本函数用于打开文件,输入数据,调用函数,输出数据,关闭文件。 */
    FILE *fp, *wf;
    int   i, a, b, c, d;

    fp = fopen("in.dat", "r");
    wf = fopen("out.dat", "w");
    for (i = 0; i < 5; i++) {
        fscanf(fp, "%d %d %d %d", &a, &b, &c, &d);
        fun(&a, &b, &c, &d);
        fprintf(wf, "a=%d,d=%d\n", a, d);
    }
    fclose(fp);
    fclose(wf);
}

4.3 解题思路

题目要求在形参指针所指的 4 个整数中找出最大值和最小值。

首先想到的方法可能是用形参指针一个一个去比较,最后得出要的结果,这种方法是行得通的,但程序编写并不简洁。

我们可以将形参中的值存储到数组中,分别定义最大值
max
和最小值
min
变量,通过循环的方式遍历数组找到最大值和最小值,最后存储到形参指针所指的地址中即可。

下面我们看代码实现:

4.4 代码实现

填写完整的代码:

#include <stdio.h>
void NONO();
void fun(int* a, int* b, int* c, int* d) {
    int buff[4] = {*a, *b, *c, *d};  // 用数组存储形参的 4 个整数,便于后续的判断
    int max = buff[0], min = buff[0], i = 0;

    for (i = 0; i < 4; i++) {  // 四个整数,故遍历 4 次
        if (buff[i] > max) {   // 最大值判断
            max = buff[i];
        }

        if (buff[i] < min) {  // 最小值判断
            min = buff[i];
        }
    }
    *a = max;  // 将最大值存储到指针 a 指向的地址中
    *d = min;  // 同上
}
main() {
    int a, b, c, d;
    printf("请输入4个整数:    ");
    scanf("%d%d%d%d", &a, &b, &c, &d);
    printf("输入数据:    %d,%d,%d,%d\n", a, b, c, d);
    fun(&a, &b, &c, &d);
    printf("最大值:%d,最小值:%d\n", a, d);
    NONO();
    getchar();
    getchar();
}

void NONO() { /* 本函数用于打开文件,输入数据,调用函数,输出数据,关闭文件。 */
    FILE *fp, *wf;
    int   i, a, b, c, d;

    fp = fopen("in.dat", "r");
    wf = fopen("out.dat", "w");
    for (i = 0; i < 5; i++) {
        fscanf(fp, "%d %d %d %d", &a, &b, &c, &d);
        fun(&a, &b, &c, &d);
        fprintf(wf, "a=%d,d=%d\n", a, d);
    }
    fclose(fp);
    fclose(wf);
}

提示:为确保代码正常运行,请在题库编程环境的对应题目中进行测试和运行。

5. 后记

本篇博客到这就结束了,如果您有疑问或建议欢迎您在留言区留言。

Spring IOC(Inversion of Control,控制反转)依赖注入是 Spring 框架的核心特性之一,旨在实现对象之间的松耦合,提升代码的可维护性、可测试性和可扩展性。下面我们将从以下几个方面深入探讨 Spring IOC 依赖注入的机制和实现原理。

一、基本概念

  1. 控制反转(Inversion of Control)

    控制反转是一种设计原则,指的是将对象的创建和管理职责从应用程序中转移到框架中。传统上,应用程序直接创建依赖对象,而在 IOC 中,控制权由容器掌握,应用只需声明所需的依赖。

  2. 依赖注入(Dependency Injection)

    依赖注入是实现控制反转的一种具体方式,通过构造函数、setter 方法或字段注入,将依赖的对象传递给使用它的对象。

二、依赖注入的实现方式

Spring 提供了三种主要的依赖注入方式:

  1. 构造器注入

    通过构造函数传入依赖对象,通常适用于需要强制依赖的情况。

    示例:

    public class UserService {
        private final UserRepository userRepository;
    
        public UserService(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    }
    
  2. Setter 注入

    通过 setter 方法设置依赖对象,适合可选依赖的场景。

    示例:

    public class UserService {
        private UserRepository userRepository;
    
        public void setUserRepository(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    }
    
  3. 字段注入

    直接在字段上使用
    @Autowired
    注解,Spring 会自动注入依赖。此方式适用于简单场景,但不利于单元测试。

    示例:

    public class UserService {
        @Autowired
        private UserRepository userRepository;
    }
    

三、Spring IOC 容器的工作原理

  1. Bean 定义

    在 Spring 中,每个被管理的对象称为 Bean。Bean 的定义通常通过 XML 配置文件或 Java 注解(如
    @Component
    ,
    @Service
    ,
    @Repository
    ,
    @Controller
    等)来描述。

  2. 容器初始化

    当 Spring 容器启动时,它会加载所有的 Bean 定义,创建 Bean 实例,并解析其依赖关系。依赖关系解析的过程主要分为以下几步:


    • Bean 创建
      :通过反射机制创建 Bean 实例。
    • 依赖解析
      :根据配置确定 Bean 的依赖关系,并实例化其依赖 Bean。
    • 注入依赖
      :将依赖对象注入到目标 Bean 中。
  3. 生命周期管理

    Spring 容器管理 Bean 的生命周期,包括创建、初始化、销毁等过程。开发者可以通过实现
    InitializingBean

    DisposableBean
    接口或使用
    @PostConstruct

    @PreDestroy
    注解来控制 Bean 的生命周期。

四、依赖注入的优势

  1. 松耦合

    通过依赖注入,类之间不再直接依赖于具体实现,而是依赖于接口或抽象类,这减少了类之间的耦合度。

  2. 可测试性

    由于依赖关系通过构造函数或 setter 方法注入,方便进行单元测试。在测试时可以使用模拟对象(Mock)来替代真实的依赖。

  3. 灵活性和可扩展性

    可以通过更改配置来替换实现,而不需要修改使用该依赖的代码,增强了系统的灵活性。

五、依赖注入的使用场景

  1. 服务层与数据访问层

    在 Web 应用中,服务层通常依赖于数据访问层的接口,通过依赖注入,可以轻松实现服务与数据访问之间的解耦。

  2. 配置与环境管理

    可以将环境相关的配置(如数据库连接、消息队列等)抽象为 Bean,通过依赖注入使不同环境下的配置可以灵活切换。

  3. 增强模块化

    在大型企业应用中,通过依赖注入可以实现模块间的清晰分离,使得各模块可以独立开发和测试。

六、示例代码

以下是一个使用 Spring IOC 依赖注入的简单示例:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.stereotype.Component;

@Component
class UserRepository {
    public void save() {
        System.out.println("User saved!");
    }
}

@Component
class UserService {
    private final UserRepository userRepository;

    // 构造器注入
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public void registerUser() {
        userRepository.save();
    }
}

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext("com.example"); // 包名
        UserService userService = context.getBean(UserService.class);
        userService.registerUser(); // 输出 "User saved!"
    }
}

七、深入的设计考虑

  1. AOP(面向切面编程)集成

    Spring IOC 依赖注入与 AOP 结合,可以在不修改业务逻辑的情况下,为 Bean 增加横切关注点(如事务管理、日志记录等)。

  2. Bean 的作用域

    Spring 支持多种 Bean 作用域,如单例(singleton)、原型(prototype)、请求(request)、会话(session)等,开发者可以根据需要选择合适的作用域。

  3. 条件注入

    Spring 允许通过条件注解(如
    @Conditional
    )来实现基于环境或条件的依赖注入,提高配置的灵活性。

  4. 性能考虑

    虽然依赖注入带来了灵活性,但在高性能场景下,要注意对象创建的开销。可以使用单例 Bean 或者通过静态工厂方法来优化性能。

2023年10月份写过一篇《
本计划在 .NET 8 中推出的 WASI 推迟到 .NET 9
》[1],根据
此问题
[2],在 .NET 9 RTM 中似乎不会有wasi-experimental,仅使用
componentize-dotnet
[3]的项目才能工作/将被更新,WASI 实验正在 NET 10 中继续进行。

2024年9月份字节码联盟发布了一篇文章《
使用 componentize-dotnet 为 .NET/C# 开发人员简化组件
》[4],文章总结了 componentize-dotnet 项目,这是一个为.NET/C#开发者提供的工具,使得将代码编译为WebAssembly组件变得简单。这个由Bytecode Alliance发起的项目是一个NuGet包,允许从.NET应用程序创建完全AOT编译的组件,为.NET开发者提供了与Rust和TinyGo相当的组件体验。文章详细介绍了如何使用.NET 9 Preview 7和 componentize-dotnet 开始构建组件,包括安装必要的软件、配置NuGet以引用实验性包源、添加 BytecodeAlliance.Componentize.DotNet.Wasm.SDK 包到项目中,并展示了如何构建和运行一个简单的WebAssembly组件。此外,文章还探讨了如何简化组件工作流程,特别是如何使用WebAssembly Interface Type (WIT)定义来实现组件间的互操作性。最后,文章提到了.NET 9最终发布时将支持通过Mono编译器生成组件,以及 componentize-dotnet 项目将如何为用户提供选择 NativeAOT-LLVM 或Mono编译器的便利。文章鼓励有兴趣的开发者加入Bytecode Alliance社区并参与 componentize-dotnet 项目。

根据这篇文章的最新的适用于 .NET 9 RC 2 和 Wasmtime 26.0.0的代码示例:
https://github.com/henrikrxn/webassembly-experiments/blob/main/dotnet-9/componentize-dotnet/README.md[5
]

相关链接:

git-commit-id-plugin
是一个 Maven 插件,用于在 Maven 项目的构建过程中自动获取 git 仓库的信息,如最后一次提交的 ID、分支名称、构建时间等,并将这些信息注入到项目的属性文件中。这对于跟踪项目版本和构建状态非常有用。

以下是如何在 Maven 项目中使用
git-commit-id-plugin
的基本步骤:

1 添加插件到 pom.xml 文件中

在你的 Maven 项目的
pom.xml
文件中,添加
git-commit-id-plugin

<plugins>
部分。

  <build>
    <plugins>
      <plugin>
        <!-- https://mvnrepository.com/artifact/pl.project13.maven/git-commit-id-plugin -->
        <groupId>pl.project13.maven</groupId>
        <artifactId>git-commit-id-plugin</artifactId>
        <version>4.0.5</version>
        <executions>
          <execution>
            <goals>
              <goal>revision</goal>
            </goals>
          </execution>
        </executions>
        <!-- 配置选项 -->
        <configuration>
          <dotGitDirectory>${project.basedir}/../.git</dotGitDirectory>
          <generateGitPropertiesFile>true</generateGitPropertiesFile>
          <failOnNoGitDirectory>false</failOnNoGitDirectory>
          <skipPoms>false</skipPoms>
          <generateGitPropertiesFilename>
            ${project.build.outputDirectory}/git.properties
          </generateGitPropertiesFilename>
          <gitDescribe>
            <!-- don't generate the describe property -->
            <skip>false</skip>
            <!-- abbrev commit id length -->
            <abbrev>8</abbrev>
          </gitDescribe>
          <includeOnlyProperties>
            <includeOnlyProperty>^git.branch$</includeOnlyProperty>
            <includeOnlyProperty>^git.build.(time|version)$</includeOnlyProperty>
            <includeOnlyProperty>^git.commit.(id|time)$</includeOnlyProperty>
            <includeOnlyProperty>^git.commit.id.abbrev$</includeOnlyProperty>
          </includeOnlyProperties>
        </configuration>
      </plugin>
    </plugins>
  </build>

2 配置插件


<configuration>
标签中,你可以配置多种选项,例如生成的属性文件的位置、包含哪些 git 属性等。默认情况下,插件会生成一个
git.properties
文件在
target/classes
目录下。

   <configuration>
       <dotGitDirectory>${project.basedir}/.git</dotGitDirectory>
       <generateGitPropertiesFile>true</generateGitPropertiesFile>
       <generateGitPropertiesFilename>${project.build.outputDirectory}/git.properties</generateGitPropertiesFilename>
       <format>properties</format>
   </configuration>
  1. 构建项目:

使用 Maven 命令构建项目,例如
mvn clean install
。在构建过程中,
git-commit-id-plugin
插件会自动运行,并在指定的位置生成包含 git 信息的
git.properties
文件。

4 查看 git.properties 文件

打开生成的
git.properties
文件,你将看到类似以下的内容,其中包含了提交时间、提交记录、分支等信息:

#Generated by Git-Commit-Id-Plugin
git.branch=main
git.build.host=heal-mac
git.build.time=2024-10-30T15\:04\:10+0800
git.build.version=1.0.0-SNAPSHOT

git.commit.id=abcdef1234567890
git.commit.id.abbrev=abcdef1
git.commit.time=2024-10-28T12\:34\:56+0800

git.commit.message.full=Initial commit
git.commit.message.short=Initial commit

git.commit.user.name=Your Name
git.commit.user.email=your.email@example.com

git.commit.author.time=2024-10-26T21\:11\:39+0800
git.commit.committer.time=2024-10-26T21\:11\:39+0800

5 在应用中使用这些信息

你可以在应用程序中读取
git.properties
文件,并使用这些信息,例如显示当前版本的 Git 分支和提交 ID。

通过这种方式,你可以利用
git-commit-id-plugin
插件来自动获取和使用 Git 的提交时间、提交记录、分支等信息,从而帮助跟踪和管理你的项目版本。

在Java中,对于基本类型可以使用“="来进行克隆,此时两个变量除了相等是没有任何关系的。

而对于
引用类型却不能简单地使用”=“进行克隆
,这与java的内存空间使用有关。

Java将内存空间分成两块,即栈和堆。

在栈中保存基本类型和引用变量;

在堆中保存对象。

对于引用变量而言,使用”=“将修改引用,而不是复制堆中的对象。

此时两个引用变量将指向同一个对象。

因此,如果一个变量对其进行修改则会改变另一个变量。

在克隆对象时,如果对象的成员变量是基本类型,则使用浅克隆即可完成。

如果对象的成员变量包括可变引用类型,则需要使用深克隆。

如果引用类型是不可变的,如String类的对象,则不必进行深克隆。