C语言架构设计之程序解耦

C语言架构设计之程序解耦

高耦合带来的问题

高耦合度的 C 语言程序会导致以下影响:

可扩展性降低

高耦合度会显著降低程序的可扩展性,这意味着在修改或增加功能时,需要更多的时间和资源,因为一个模块的变化可能需要影响到多个模块。这里有一个示例来说明高耦合度如何降低可扩展性:

假设有一个简单的库存管理系统,包含两个模块:Inventory(库存管理)和 Sales(销售管理)。它们高度耦合,Sales 模块直接依赖于 Inventory 模块。

    // Inventory 模块
    
    #include <stdio.h>
    
    int available_quantity = 100;
    
    void update_quantity(int sold_quantity) {
        available_quantity -= sold_quantity;
    }
    
    // Sales 模块
    
    #include <stdio.h>
    
    extern int available_quantity;
    
    void make_sale(int sold_quantity) {
        if (sold_quantity <= available_quantity) {
            printf("Sale successful!\n");
            update_quantity(sold_quantity); // 直接调用 Inventory 模块的函数
        } else {
            printf("Insufficient quantity!\n");
        }
    }
    
    int main() {
        make_sale(50);
        make_sale(80);
        return 0;
    }

在这个例子中,Sales 模块直接调用了 Inventory 模块的 update_quantity 函数来更新库存数量。这种直接依赖关系造成了高耦合性,使得对库存管理的变更直接影响到销售管理。

现在,如果要增加新功能,比如增加销售时的折扣功能,可能需要对 update_quantity 函数进行修改,而这会直接影响到 Sales 模块的行为,因为它依赖于 update_quantity 函数。这种耦合性限制了系统的可扩展性,增加了维护和修改的难度。

可维护性差

高度耦合的 C 语言程序可能导致可维护性差的几个方面:

难以理解和修改:当模块之间紧密耦合时,一个模块的修改可能会影响到其他多个模块,导致代码关系复杂、难以理解和维护。这使得需要花费更多时间来理解代码逻辑,并且修改一个模块可能会产生不可预期的影响。

难以调试:高度耦合的程序中,问题可能不仅仅在于一个模块,而是涉及到多个模块的交互。这使得调试问题变得更加复杂,需要跟踪多个模块之间的交互,增加了定位和解决问题的难度。

难以重构:当需要对程序进行重构或优化时,高度耦合的程序使得难以安全地对代码进行更改。因为修改一个模块可能会影响到其他多个模块,可能需要大规模的重构才能保证整个程序的稳定性。

下面是一个示例,演示高度耦合的两个模块,一个修改可能会影响到另一个的情况:

    // Module A
    
    #include <stdio.h>
    
    int global_var = 10;
    
    void process_data(int data) {
        printf("Processing data in Module A\n");
        global_var += data;
    }
    
    // Module B
    
    #include <stdio.h>
    
    extern int global_var;
    
    void update_data(int value) {
        printf("Updating data in Module B\n");
        if (value > 0) {
            process_data(value); // 直接调用 Module A 的函数
        }
    }
    
    // Main
    
    int main() {
        update_data(5);
        return 0;
    }

这个示例中,Module B 直接依赖于 Module Aprocess_data 函数。如果需要修改 Module A 中的 process_data 函数,可能会影响到 Module B 中的 update_data 函数。这种紧密耦合的关系使得对一个模块的修改可能需要修改其他模块,降低了代码的可维护性。

执行效率降低

高度耦合的 C 语言程序可能导致执行效率降低的几个方面:

传递额外的数据:当模块之间高度耦合时,可能需要传递额外的数据给其他模块,这可能会增加函数参数或者全局变量的数量。参数的传递可能需要额外的内存和 CPU 时间,特别是对于复杂的数据结构或大量参数的情况。

多次间接调用:高度耦合的程序通常包含多层次的函数调用,一个函数调用另一个函数,后者再调用下一个函数,以此类推。这些多次间接调用可能导致额外的函数调用开销和栈操作。

不必要的计算:当一个模块的修改会影响到其他多个模块时,可能会导致一些不必要的计算。因为某个模块的修改可能会导致其他模块重新计算或者重新处理某些数据。

示例代码如下:

假设有一个高度耦合的程序,其中的两个模块之间紧密关联,一个模块修改会导致另一个模块执行额外的计算。比如有一个简单的计算器程序:

    // Calculator 模块
    
    #include <stdio.h>
    
    extern int global_value;
    
    int add(int a, int b) {
        return a + b + global_value; // 使用了全局变量
    }
    
    // Client 模块
    
    #include <stdio.h>
    
    int global_value = 10;
    
    int main() {
        int result = add(5, 7); // 在调用 add 函数时,会使用全局变量
        printf("Result: %d\n", result);
        return 0;
    }
    

这个例子中,Calculator 模块的 add 函数使用了全局变量 global_value。如果 global_value 在其他地方被修改,这可能会导致 add 函数的结果变化,从而影响整个程序的执行结果。这种紧密的耦合关系可能会导致不必要的计算和依赖关系。

代码复用困难

高度耦合的程序中,很难提取和重用单独的模块或功能,因为这些模块可能与其他模块强相关。假设有一个电子商务系统,有两个模块:OrderProcessing(订单处理)和 InventoryManagement(库存管理)。它们之间存在高度耦合。

    // OrderProcessing 模块
    
    #include <stdio.h>
    
    void process_order(int product_id, int quantity) {
        printf("Processing order for product ID %d, quantity: %d\n", product_id, quantity);
        // 一些处理...
        update_inventory(product_id, quantity); // 调用库存管理模块的函数
    }
    
    // InventoryManagement 模块
    
    #include <stdio.h>
    
    void update_inventory(int product_id, int quantity) {
        printf("Updating inventory for product ID %d, quantity: %d\n", product_id, quantity);
        // 一些处理...
    }
    
    // Main 模块
    
    #include <stdio.h>
    
    void process_order(int product_id, int quantity); // 声明订单处理模块的函数
    
    int main() {
        process_order(101, 5); // 处理订单
        return 0;
    }
    

这个示例中,OrderProcessing 模块在订单处理时直接调用了 InventoryManagement 模块的 update_inventory 函数。这种耦合使得想要单独复用 OrderProcessing 模块而不涉及 InventoryManagement 模块变得困难,因为它们之间相互依赖。

可读性和可理解性差

代码的耦合度高意味着不同模块之间的关系复杂,导致代码难以理解和阅读。低可读性和可理解性通常与高度耦合相关。当代码高度耦合时,阅读代码变得困难,因为需要理解整个系统的各个部分才能理解其中一个部分的功能。这使得代码难以被新人理解,也增加了维护成本,因为任何修改都可能对整个系统造成意想不到的影响。

以下是一个简单的 C 语言示例,展示了高度耦合的情况:

    #include <stdio.h>
    
    // 全局变量
    int a = 5;
    
    // 函数1,依赖于全局变量
    void function1() {
        printf("Function 1: %d\n", a);
    }
    
    // 函数2,修改了全局变量
    void function2() {
        a += 10;
    }
    
    int main() {
        function1();  // 输出: Function 1: 5
        function2();
        function1();  // 输出: Function 1: 15
    
        return 0;
    }
    

这个例子中,函数 function1function2 都依赖于全局变量 a。虽然这段代码很简单,但是它展示了高度耦合的特征:function1function2 之间存在强烈的依赖关系,修改了全局变量 a 的值会影响到 function1 的输出结果。这种依赖关系会使得代码难以维护和理解。

测试困难

高度耦合的C语言程序会导致测试困难的主要原因在于难以单独测试模块或功能。耦合度高的程序模块间相互依赖性强,这导致了以下测试困难:

  1. 难以分离单元测试
  • 因为模块之间紧密耦合,很难单独测试一个模块,而不涉及其他模块。
  • 单元测试需要模拟模块的周边环境或依赖,但高度耦合的程序使得分离这些依赖非常困难。
  1. 测试覆盖范围难以确定
  • 由于模块之间相互交织,测试一个模块可能会触发其他模块的行为,难以确定测试覆盖的范围。
  • 修改一个模块可能会对其他模块产生意想不到的影响,导致测试需要覆盖更多的代码路径。
  1. 依赖管理困难
  • 高度耦合的程序可能依赖于许多全局变量、函数或外部库,这些依赖关系需要精确管理,否则测试结果可能会受到未预期的影响。

让我们来看一个示例:

    #include <stdio.h>
    
    // 模块A
    int calculate(int x, int y) {
        extern int global_var;
    
        global_var = x + y;
        return global_var;
    }
    
    // 模块B
    void printResult() {
        extern int global_var;
        printf("Result: %d\n", global_var);
    }
    
    // 主程序
    int main() {
        int a = 5, b = 10;
        calculate(a, b);
        printResult();
        return 0;
    }
    

示例中,模块A负责计算并设置全局变量 global_var 的值,而模块B则打印这个全局变量的值。这两个模块之间紧密耦合,依赖于同一个全局变量。这会导致测试困难:

  • 如果想单独测试 calculate 函数,很难做到,因为它直接修改了全局变量 global_var,而 printResult 函数也使用了这个全局变量,所以必须同时测试两个函数。
  • 在修改 calculate 函数时,可能会无意中影响 printResult 函数的行为,因为它们共享全局变量,这增加了测试的不确定性和复杂性。

如何解耦

解耦,即减少模块间的依赖,提高代码的灵活性和可维护性,核心在于数据函数方法解耦。

以下是一些解决高耦合的方法和技巧:

方法一:使用抽象层

  1. 抽象数据结构和函数接口:通过定义清晰的数据结构和接口函数,将模块间的通信限制在接口层,而不是直接操作内部数据结构。

在C语言中,要抽象数据结构和函数接口,可以通过结构体、函数指针和头文件来实现。这样可以将数据结构的实现细节隐藏起来,使得用户只需关注接口和操作,而不必担心具体实现。

抽象数据结构的步骤:

  • 使用结构体定义数据结构
// 以栈为例,定义栈的结构体  
#define MAX_SIZE 100

typedef struct {  
    int items[MAX_SIZE];  
    int top;  
} Stack;

  • 编写操作数据结构的函数
    // 初始化栈  
    void initialize(Stack *stack) {  
        stack->top = -1;  
    }

    // 入栈操作  
    void push(Stack *stack, int value) {  
        if (stack->top == MAX_SIZE - 1) {  
            printf("Stack overflow\n");  
            return;  
        }  
        stack->items[++stack->top] = value;  
    }

    // 出栈操作  
    int pop(Stack *stack) {  
        if (stack->top == -1) {  
            printf("Stack underflow\n");  
            return -1;  
        }  
        return stack->items[stack->top--];  
    }

    // 获取栈顶元素  
    int peek(Stack *stack) {  
        if (stack->top == -1) {  
            printf("Stack is empty\n");  
            return -1;  
        }  
        return stack->items[stack->top];  
    }

    // 检查栈是否为空  
    int isEmpty(Stack *stack) {  
        return stack->top == -1;  
    }

  • 创建头文件来声明接口
    // stack.h  
    #ifndef STACK_H  
    #define STACK_H
    #define MAX_SIZE 100
    typedef struct {  
        int items[MAX_SIZE];  
        int top;  
    } Stack;

    void initialize(Stack *stack);  
    void push(Stack *stack, int value);  
    int pop(Stack *stack);  
    int peek(Stack *stack);  
    int isEmpty(Stack *stack);
    #endif /* STACK_H */
  • 使用抽象的数据结构和函数
    #include <stdio.h>
    #include "stack.h"
    
    int main() {
        Stack stack;
        initialize(&stack);
    
        push(&stack, 10);
        push(&stack, 20);
        push(&stack, 30);
    
        printf("Top element: %d\n", peek(&stack));
    
        while (!isEmpty(&stack)) {
            printf("Popped: %d\n", pop(&stack));
        }
    
        return 0;
    }
    

这种方式将数据结构的具体实现隐藏在内部,并提供了清晰简洁的接口供用户使用。用户只需包含头文件并调用相应的函数即可操作数据结构,而不必关心底层实现的细节。

  1. 模块化编程:将功能划分为独立的模块,每个模块专注于完成特定的任务。这样可以降低模块之间的耦合度。

模块化编程是通过函数和文件分割来组织代码,使其更易维护、理解和重用。下面是实现模块化编程的步骤,并附有一个简单的示例:

步骤:

  1. 划分模块:

将代码分割为独立的功能模块。每个模块可以处理特定任务或执行相关功能。

  1. 创建头文件:

对于每个模块,创建相应的头文件(.h文件),其中包含函数声明和公共变量的声明。头文件充当接口,使其他文件能够访问模块功能而不暴露其实现细节。

  1. 编写实现文件:

为每个模块编写实现文件(.c文件),包含模块功能的具体代码。

  1. 函数调用:

在主程序或其他模块中使用函数调用来使用模块提供的功能。

示例:

假设我们要实现一个简单的计算器,分为加法和减法两个模块,每个模块有其独立的头文件和实现文件。

add.h - 加法模块的头文件:

    #ifndef ADD_H
    #define ADD_H
    
    int add(int a, int b);
    
    #endif /* ADD_H */
    

add.c - 加法模块的实现文件:

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

sub.h - 减法模块的头文件:

    #ifndef SUB_H
    #define SUB_H
    
    int subtract(int a, int b);
    
    #endif /* SUB_H */
    

sub.c - 减法模块的实现文件:

    #include "sub.h"
    
    int subtract(int a, int b) {
        return a - b;
    }
    

主程序 - main.c

    #include <stdio.h>
    #include "add.h"
    #include "sub.h"
    
    int main() {
        int num1 = 10, num2 = 5;
    
        printf("Addition: %d\n", add(num1, num2));
        printf("Subtraction: %d\n", subtract(num1, num2));
    
        return 0;
    }
    

示例中,每个模块都有其对应的头文件和实现文件。main.c文件中通过包含add.hsub.h头文件,使用了add()subtract()函数,而不需要了解它们的具体实现细节。这种模块化的方式让代码更易读、更易维护,并提高了代码的重用性。

方法二:模块间通信

  1. 使用回调函数:允许一个模块通过回调函数将自己的功能传递给另一个模块,而不需要直接调用对方的函数。

模块间通信可以通过回调函数实现。回调函数是一种指向函数的指针,它作为参数传递给其他函数,以便在特定事件发生时调用。通过回调函数,一个模块可以调用另一个模块中的函数,实现模块间的通信。

下面是一个简单的示例,展示如何使用回调函数在两个模块之间进行通信:

模块一:包含回调函数的模块

    #include <stdio.h>
    
    // 定义回调函数的原型
    typedef void (*CallbackFunction)(int);
    
    // 模块一的函数,接受回调函数作为参数
    void performOperation(int data, CallbackFunction callback) {
        printf("Performing operation with data: %d\n", data);
    
        // 模拟操作完成后调用回调函数通知模块二
        int result = data * 2; // 一些操作,这里简单地将数据乘以2作为示例
    
        // 调用回调函数通知模块二
        callback(result);
    }
    

模块二:调用模块一的函数并定义回调函数

    #include <stdio.h>
    
    // 模块二中的回调函数
    void callbackFunction(int result) {
        printf("Received result from module one: %d\n", result);
    }
    
    // 模块二的主函数
    int main() {
        int data = 5; // 要处理的数据
    
        // 调用模块一的函数,并传递回调函数作为参数
        performOperation(data, callbackFunction);
    
        return 0;
    }
    

示例中,模块一包含了一个函数 performOperation,该函数接受一个整数和一个回调函数作为参数。它执行一些操作(这里简单地将输入数据乘以2),然后调用传递的回调函数,并将结果传递给模块二。

模块二中定义了回调函数 callbackFunction,它接收来自模块一的结果,并在收到结果后进行处理。

通过这种方式,模块一和模块二之间通过回调函数实现了通信。模块一执行某些操作后,通过回调函数通知模块二,并将处理的结果传递给模块二。

  1. 消息传递:通过定义消息格式,在模块之间传递消息而不是直接调用对方的函数,例如使用消息队列或发布-订阅模式。

可以通过消息传递机制实现模块间的通信。这通常涉及定义消息结构、发送消息和接收消息的过程。下面是一个简单的示例,展示如何使用消息传递在两个模块之间进行通信:

模块一:发送消息的模块

    #include <stdio.h>
    
    // 定义消息结构体
    typedef struct {
        int messageType;
        int data;
    } Message;
    
    // 模块一的函数,用于发送消息
    void sendMessage(Message *message) {
        // 在实际应用中,这里可以是消息的发送逻辑,比如通过队列、共享内存等方式发送消息给模块二
        printf("Sending message with type %d and data %d\n", message->messageType, message->data);
    }
    

模块二:接收消息的模块

    #include <stdio.h>
    
    // 定义消息结构体
    typedef struct {
        int messageType;
        int data;
    } Message;
    
    // 模块二的函数,用于接收消息并处理
    void receiveMessage(Message *message) {
        // 在实际应用中,这里可以是接收消息的逻辑,处理收到的消息
        printf("Received message with type %d and data %d\n", message->messageType, message->data);
    
        // 在这里可以根据消息类型执行不同的操作
        // 比如根据 messageType 调用不同的处理函数等
    }
    
    // 模块二的主函数
    int main() {
        // 模拟消息的接收过程
        Message receivedMessage;
    
        // 假设在实际应用中接收到了消息,然后进行处理
        receivedMessage.messageType = 1; // 消息类型
        receivedMessage.data = 10; // 消息数据
    
        // 处理接收到的消息
        receiveMessage(&receivedMessage);
    
        return 0;
    }
    

示例中,模块一定义了一个 sendMessage 函数,它负责发送消息。模块二定义了一个 receiveMessage 函数,用于接收消息并进行处理。

消息被定义为一个结构体 Message,其中包含了消息类型 messageType 和消息数据 data。在实际应用中,模块一可以通过某种方式(例如队列、共享内存等)将消息发送给模块二。模块二则可以接收到消息后进行处理,根据消息类型执行相应的操作。

需要注意的是,示例中的消息传递是简化的,实际应用中可能需要考虑线程安全、消息队列的管理等问题。在真实的系统中,可能会使用更复杂的通信机制来确保消息的可靠传递和处理。

方法三:依赖注入

  1. 参数化模块:通过参数传递所需的依赖,而不是在模块内部直接引用其他模块的函数或数据。

在C语言中,依赖注入通常不像在一些高级语言中那样直接实现,因为C语言本身没有内建的依赖注入机制。但是,你可以使用一些设计模式和技巧来模拟依赖注入的概念。

依赖注入的核心思想是将一个对象的依赖关系从该对象本身解耦,通常通过构造函数或者函数参数来传递依赖项。在C语言中,你可以使用函数指针、结构体和回调函数等方法来实现类似的效果。

下面是一个简单的示例,演示了如何使用函数指针来实现简单的依赖注入:

    #include <stdio.h>
    
    // 定义一个接口
    typedef struct {
        void (*sendMessage)(const char *);
    } MessageService;
    
    // 实现一个消息服务
    void emailService(const char *message) {
        printf("Sending email: %s\n", message);
    }
    
    void textService(const char *message) {
        printf("Sending text: %s\n", message);
    }
    
    // 业务逻辑函数,它接受一个消息服务作为参数
    void processMessage(MessageService *service, const char *message) {
        service->sendMessage(message);
    }
    
    int main() {
        // 创建不同的消息服务
        MessageService email = {&emailService};
        MessageService text = {&textService};
    
        // 使用不同的服务发送消息
        processMessage(&email, "Hello, via email!");
        processMessage(&text, "Hello, via text message!");
    
        return 0;
    }
    

例子中,MessageService结构体定义了一个消息服务的接口,包含一个指向发送消息函数的函数指针。emailServicetextService函数实现了具体的消息发送功能。processMessage函数接受一个MessageService类型的指针和消息内容,然后调用传入的消息服务的发送消息函数来发送消息。

这种方法通过函数指针和结构体实现了一种简单的依赖注入,允许你在运行时决定使用哪种具体的消息服务,从而达到解耦的效果。

方法四:接口设计

  1. 明确定义接口:定义清晰、简洁的接口规范,降低模块之间的耦合度,避免直接访问对方的内部实现。

在C语言中,要定义清晰、简洁的接口规范并降低模块之间的耦合度,可以采用一些设计原则和技巧,如抽象、封装和信息隐藏。这些原则有助于确保模块间的独立性,避免直接访问对方的内部实现。

  • 使用抽象数据类型 (ADT)

抽象数据类型是一种将数据类型的表示与其相关操作分离的方法。在C语言中,可以通过结构体和函数指针来实现ADT。例如,假设我们有一个队列ADT,可以这样设计:

queue.h - (接口定义)

    #ifndef QUEUE_H
    #define QUEUE_H
    
    // 定义队列结构体(隐藏内部实现)
    typedef struct Queue Queue;
    
    // 创建并返回队列
    Queue* createQueue();
    
    // 销毁队列
    void destroyQueue(Queue* queue);
    
    // 向队列中添加元素
    void enqueue(Queue* queue, int value);
    
    // 从队列中移除元素
    int dequeue(Queue* queue);
    
    // 检查队列是否为空
    int isEmpty(Queue* queue);
    
    #endif /* QUEUE_H */
    
  • 封装实现细节

在单独的源文件中实现接口中声明的函数,这样其他模块只能通过接口函数来操作数据,而无需了解其内部实现。例如:

queue.c - (实现)

    #include <stdlib.h>
    #include "queue.h"
    
    // 结构体定义(隐藏内部实现)
    struct Queue {
        // 内部数据结构
        int* elements;
        int front;
        int rear;
        int capacity;
    };
    
    Queue* createQueue() {
        // 实现创建队列的逻辑
    }
    
    void destroyQueue(Queue* queue) {
        // 实现销毁队列的逻辑
    }
    
    void enqueue(Queue* queue, int value) {
        // 实现向队列中添加元素的逻辑
    }
    
    int dequeue(Queue* queue) {
        // 实现从队列中移除元素的逻辑
    }
    
    int isEmpty(Queue* queue) {
        // 实现检查队列是否为空的逻辑
    }
    
  • 信息隐藏

只在接口中公开必要的函数和数据类型,隐藏其他实现细节。这样可以避免其他模块直接访问内部数据结构,从而减少耦合度。模块之间只能通过公开的接口函数进行交互。

这种设计方法使得模块间的耦合度降低,因为模块之间只通过接口函数通信,而不需要了解对方的内部实现细节。这有助于提高代码的可维护性和可扩展性,同时也使得代码更易于理解和调试。

方法五:单一职责原则

  1. 每个模块只做一件事:确保每个模块只有一个明确的功能,避免模块功能的交叉和重叠。

在C语言程序设计中,实现每个模块只做一件事可以通过以下方法:

  • 模块化编程

将程序分解成小的、相互独立的模块,每个模块负责执行一个特定的任务或完成一个明确的功能。这样做有助于降低复杂度,提高代码可读性和维护性。

  • 模块功能的定义和分离

确保每个模块都有清晰的功能定义,不要让一个模块负责过多的任务。模块之间的功能应该明确分离,避免交叉和重叠。

  • 模块之间的接口和通信

模块之间通过良好定义的接口进行通信,这样可以降低耦合度,使得模块更容易被单独测试和修改。合理的接口设计可以确保模块功能的清晰和独立性。

示例:

假设我们有一个简单的学生成绩管理系统,需要实现计算学生的平均分、最高分和最低分。

我们可以创建三个独立的模块来完成这些任务:

Module 1: 计算平均分

    // average.c
    
    #include <stdio.h>
    
    float calculateAverage(int grades[], int numGrades) {
        float sum = 0.0;
        
        for (int i = 0; i < numGrades; ++i) {
            sum += grades[i];
        }
        
        return sum / numGrades;
    }
    

Module 2: 计算最高分

    // highest.c
    
    #include <stdio.h>
    
    int findHighest(int grades[], int numGrades) {
        int highest = grades[0];
        
        for (int i = 1; i < numGrades; ++i) {
            if (grades[i] > highest) {
                highest = grades[i];
            }
        }
        
        return highest;
    }
    

Module 3: 计算最低分

    // lowest.c
    
    #include <stdio.h>
    
    int findLowest(int grades[], int numGrades) {
        int lowest = grades[0];
        
        for (int i = 1; i < numGrades; ++i) {
            if (grades[i] < lowest) {
                lowest = grades[i];
            }
        }
        
        return lowest;
    }
    

每个模块都只完成一个特定的功能:计算平均分、最高分或最低分。这样的模块设计使得代码更易于理解、测试和维护。

Read more

Ubuntu 22.04 安装 Z-Shell (ZSH) 跟 Oh-My-Zsh

Ubuntu 22.04 安装 Z-Shell (ZSH) 跟 Oh-My-Zsh

Z shell 是有史以来功能最强大的 shell 之一,也是速度最快的 shell 之一。它提供的功能是你在其他任何地方都找不到的,比如内置拼写检查、代码语法高亮等。你甚至可以对命令提示符进行配置,以显示有关系统状态的有用信息,而无需键入任何内容。例如:拼写更正,文件和命令的制表符补全等。 更新系统 执行如下指令更新系统软件: $ sudo apt update && sudo apt dist-upgrade -y 执行如下指令安装相关软件: $ sudo apt install build-essential curl file git 因为我已经按转过,没有提示Y/N。请选择Y继续安装。 安装zsh 现在系统已经是最新状态,并且安装了需要的第三方软件,执行如下指令安装zsh $ sudo apt install zsh 提示是否继续,请输入Y继续安装。

By jackie ma
ubuntu 22.04安装ibus中文输入法

ubuntu 22.04安装ibus中文输入法

安装中文语言支持 打开“设置”,“Region & Language”,“Manage Installed Languages” 点击“Install/Remove Languages…” 选择“Chinese(simplified)”,勾选之后点击“Apply”等待安装完成 之后,在“Keyboard Input Method system”,选择“IBus”,“Close” 再可以执行如下操作安装相关的软件 $ sudo apt-get install ibus ibus-clutter ibus-gtk ibus-gtk3 ibus-qt4 $ im-config -s ibus $ sudo apt-get install ibus-pinyin $ ibus-setup 最后执行sudo reboot重启系统 添加中文输入法 重启起来后,打开“

By jackie ma
  •   渝ICP备2023013474号-1   •   渝公网安备50019002504014号