Ubuntu入门学习教程,从入门到精通, Ubuntu 22.04中的C/C++编程(18)

Ubuntu 22.04中的C/C++编程

18.1 C/C++编程基础

18.1.1 开发环境部署

步骤1:安装GCC/G++编译器

bash 复制代码
# 更新软件包列表
sudo apt update

# 安装build-essential(包含GCC、G++、make等)
sudo apt install build-essential -y

# 验证安装
gcc --version  # 查看GCC版本(Ubuntu 22.04默认11.x)
g++ --version  # 查看G++版本

# 安装调试工具
sudo apt install gdb valgrind -y

# 安装开发文档
sudo apt install manpages-dev manpages-posix-dev -y

# 安装CMake(现代C++构建工具)
sudo apt install cmake -y

步骤2:配置开发环境

bash 复制代码
# 设置编辑器(以VS Code为例)
# 1. 下载并安装VS Code
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
sudo install -o root -g root -m 644 packages.microsoft.gpg /etc/apt/trusted.gpg.d/
echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/trusted.gpg.d/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" | sudo tee /etc/apt/sources.list.d/vscode.list
sudo apt update
sudo apt install code -y

# 2. 安装C/C++扩展
code --install-extension ms-vscode.cpptools

# 3. 配置头文件路径
# 在VS Code中按Ctrl+Shift+P,输入C/C++: Edit Configurations (UI)
# 设置包含路径: /usr/include/**

# 安装clangd(代码补全)
sudo apt install clangd -y

18.1.2 定义变量与数据类型

C语言示例:

c 复制代码
#include <stdio.h>
#include <limits.h>
#include <float.h>

int main() {
    // ===== 基本数据类型 =====
    
    // 整型
    char c = 'A';              // 1字节,-128到127或0到255
    unsigned char uc = 255;    // 无符号字符
    
    short s = 32767;           // 2字节,-32768到32767
    unsigned short us = 65535; // 无符号短整型
    
    int i = 2147483647;        // 4字节,-2^31到2^31-1
    unsigned int ui = 4294967295U; // 无符号整型,需加U后缀
    
    long l = 9223372036854775807L; // 8字节(64位系统)
    long long ll = 9223372036854775807LL; // 至少8字节
    
    // 浮点型
    float f = 3.14159f;        // 4字节,6位小数精度,需加f后缀
    double d = 3.141592653589793; // 8字节,15位小数精度
    long double ld = 3.1415926535897932384626L; // 至少16字节
    
    // ===== 类型大小 =====
    printf("数据类型大小:\n");
    printf("char: %zu 字节\n", sizeof(char));
    printf("int: %zu 字节\n", sizeof(int));
    printf("long: %zu 字节\n", sizeof(long));
    printf("float: %zu 字节\n", sizeof(float));
    printf("double: %zu 字节\n", sizeof(double));
    
    // ===== 常量 =====
    const int MAX_SIZE = 100;  // 常量,不可修改
    // MAX_SIZE = 200; // 错误!
    
    // 枚举
    enum Weekday { MON=1, TUE, WED, THU, FRI, SAT, SUN };
    enum Weekday today = WED;  // 值为3
    
    // ===== 类型转换 =====
    int num = 10;
    float f_num = (float)num / 3; // 显式转换,结果为3.333...
    printf("转换结果: %f\n", f_num);
    
    // ===== 限定符 =====
    volatile int sensor_data; // 告诉编译器该变量可能被外部改变
    register int counter;    // 建议编译器将变量放入寄存器(现代编译器通常忽略)
    
    return 0;
}

编译与运行:

bash 复制代码
# 保存为datatype.c
# 编译(添加-Wall显示所有警告)
gcc -Wall -o datatype datatype.c

# 运行
./datatype

C++示例:

cpp 复制代码
#include <iostream>
#include <limits>
#include <cstdint>  // C++11固定宽度整数类型

using namespace std;

int main() {
    // ===== C++基本类型 =====
    // 与C相同,但增加了bool和wchar_t
    
    bool flag = true;          // true或false,1字节
    wchar_t wch = L'中';       // 宽字符
    
    // C++11固定宽度整数(推荐)
    int8_t i8 = 127;           // 精确8位有符号整数
    uint8_t ui8 = 255;         // 精确8位无符号整数
    int16_t i16 = 32767;
    uint32_t ui32 = 4294967295;
    int64_t i64 = 9223372036854775807;
    
    // ===== 常量 =====
    const int MAX_USERS = 100;      // 编译期常量
    constexpr double PI = 3.14159;  // C++11编译期常量表达式
    
    // ===== 引用(C++特有)=====
    int original = 10;
    int &ref = original;            // 引用,必须初始化
    ref = 20;                       // original也变为20
    cout << "original: " << original << endl; // 输出20
    
    // ===== auto类型推导(C++11)=====
    auto number = 42;               // 自动推导为int
    auto pi = 3.14159;              // 自动推导为double
    auto text = "C++17";            // 自动推导为const char*
    
    // ===== decltype =====
    decltype(number) another_num;   // another_num类型与number相同
    
    // ===== 类型别名 =====
    using IntArray = int[10];       // C++11类型别名
    typedef unsigned long ulong;    // C风格
    
    return 0;
}

编译C++:

bash 复制代码
# 保存为cpp_datatype.cpp
g++ -std=c++17 -Wall -o cpp_datatype cpp_datatype.cpp

# 运行
./cpp_datatype

18.1.3 表达式与运算符

c 复制代码
#include <stdio.h>
#include <stdbool.h>  // C99布尔类型

int main() {
    int a = 10, b = 20, c;
    
    // ===== 算术运算符 =====
    c = a + b;      // 加法
    c = a - b;      // 减法
    c = a * b;      // 乘法
    c = b / a;      // 除法,结果为2
    c = b % a;      // 取模,结果为0
    
    // 自增自减
    c = a++;        // 先赋值后自增,c=10, a=11
    c = ++b;        // 先自增后赋值,c=21, b=21
    
    // ===== 关系运算符 =====
    bool result;
    result = (a == b);  // 相等
    result = (a != b);  // 不等
    result = (a > b);   // 大于
    result = (a < b);   // 小于
    result = (a >= b);  // 大于等于
    result = (a <= b);  // 小于等于
    
    // ===== 逻辑运算符 =====
    result = (a > 5) && (b < 30);  // 逻辑与
    result = (a > 15) || (b < 30);  // 逻辑或
    result = !(a == b);             // 逻辑非
    
    // ===== 位运算符 =====
    unsigned int x = 5;  // 0101
    unsigned int y = 3;  // 0011
    
    c = x & y;   // 位与,0001 = 1
    c = x | y;   // 位或,0111 = 7
    c = x ^ y;   // 异或,0110 = 6
    c = ~x;      // 取反,111...1010 = -6(有符号)
    c = x << 1;  // 左移,1010 = 10
    c = x >> 1;  // 右移,0010 = 2
    
    // ===== 赋值运算符 =====
    c = 10;    // 简单赋值
    c += 5;    // 等价于 c = c + 5
    c -= 3;    // 等价于 c = c - 3
    c *= 2;    // 等价于 c = c * 2
    c /= 4;    // 等价于 c = c / 4
    c %= 3;    // 等价于 c = c % 3
    
    // ===== 条件运算符(三目)=====
    c = (a > b) ? a : b;  // 如果a>b则c=a,否则c=b
    
    // ===== 逗号运算符 =====
    c = (a = 5, b = 10, a + b);  // 依次执行,结果为最后一个表达式
    
    // ===== sizeof运算符 =====
    printf("int大小: %zu 字节\n", sizeof(int));
    
    // ===== 类型转换 =====
    float f = (float)a / 3;  // 显式转换
    int truncated = 10.5;    // 隐式截断,结果为10
    
    return 0;
}

18.1.4 程序结构

c 复制代码
#include <stdio.h>

// ===== 全局变量 =====
int global_var = 100;

// 函数声明(原型)
void print_message(const char *msg);
int add(int x, int y);

int main(int argc, char *argv[]) {
    // argc: 参数数量
    // argv: 参数数组
    
    // ===== 顺序结构 =====
    printf("程序开始\n");
    printf("参数数量: %d\n", argc);
    
    // 打印所有参数
    for (int i = 0; i < argc; i++) {
        printf("参数 %d: %s\n", i, argv[i]);
    }
    
    // ===== 选择结构 =====
    int choice = 2;
    
    // if-else
    if (choice == 1) {
        printf("选择1\n");
    } else if (choice == 2) {
        printf("选择2\n");
    } else {
        printf("其他选择\n");
    }
    
    // switch-case
    switch (choice) {
        case 1:
            printf("Switch: 选择1\n");
            break;  // 必须有break,否则会fall-through
        case 2:
            printf("Switch: 选择2\n");
            break;
        default:
            printf("Switch: 默认\n");
            break;
    }
    
    // ===== 循环结构 =====
    
    // for循环
    printf("\nFor循环:\n");
    for (int i = 0; i < 5; i++) {
        printf("i = %d\n", i);
    }
    
    // while循环
    printf("\nWhile循环:\n");
    int count = 0;
    while (count < 5) {
        printf("count = %d\n", count);
        count++;
    }
    
    // do-while循环(至少执行一次)
    printf("\nDo-While循环:\n");
    int num = 0;
    do {
        printf("num = %d\n", num);
        num++;
    } while (num < 5);
    
    // break和continue
    printf("\nBreak和Continue:\n");
    for (int i = 0; i < 10; i++) {
        if (i == 3) continue;  // 跳过3
        if (i == 7) break;     // 在7处退出
        printf("i = %d\n", i);
    }
    
    // ===== 函数调用 =====
    print_message("Hello from main!");
    int sum = add(10, 20);
    printf("10 + 20 = %d\n", sum);
    
    // ===== 返回语句 =====
    return 0;  // 返回0表示成功
}

// 函数定义
void print_message(const char *msg) {
    printf("函数消息: %s\n", msg);
}

int add(int x, int y) {
    return x + y;  // 返回值
}

编译与运行:

bash 复制代码
gcc -o program_structure program_structure.c

# 带参数运行
./program_structure arg1 arg2 arg3

18.1.5 数组和赋值

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    // ===== 一维数组 =====
    int numbers[5];           // 声明5个整数的数组
    int initialized[5] = {1, 2, 3, 4, 5};  // 声明并初始化
    
    // 省略数组大小(自动推断)
    int auto_size[] = {10, 20, 30, 40, 50};  // 自动确定大小为5
    
    // 访问和赋值
    numbers[0] = 100;         // 第一个元素
    numbers[1] = 200;         // 第二个元素
    numbers[4] = 500;         // 最后一个元素(索引从0开始)
    
    // 越界访问(危险!)
    // numbers[5] = 600;      // 未定义行为,可能导致崩溃
    
    // 遍历数组
    printf("数组元素: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", numbers[i]);
    }
    printf("\n");
    
    // ===== 数组大小计算 =====
    int size = sizeof(initialized) / sizeof(initialized[0]);
    printf("数组元素个数: %d\n", size);
    
    // ===== 二维数组 =====
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    
    printf("矩阵:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }
    
    // ===== 字符数组(字符串)=====
    char str1[20];            // 字符数组
    char str2[] = "Hello";    // 自动计算大小(6,包括\0)
    char str3[20] = "World";
    
    // 字符串赋值(不能用=,需用strcpy)
    strcpy(str1, "Ubuntu");
    
    // 字符串连接
    strcat(str1, " Linux");
    printf("字符串: %s\n", str1);
    
    // 字符串长度
    printf("字符串长度: %zu\n", strlen(str1));
    
    // ===== 变长数组(C99)=====
    int n = 5;
    int dynamic_array[n];     // 变量长度数组(VLA)
    
    for (int i = 0; i < n; i++) {
        dynamic_array[i] = i * 10;
    }
    
    // ===== 数组作为函数参数 =====
    void print_array(int arr[], int length) {
        for (int i = 0; i < length; i++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
    }
    
    print_array(initialized, 5);
    
    return 0;
}

C++数组示例:

cpp 复制代码
#include <iostream>
#include <array>      // C++11 std::array
#include <vector>     // 动态数组
#include <string>     // C++字符串

using namespace std;

int main() {
    // ===== C风格数组(与C相同)=====
    int arr[5] = {1, 2, 3, 4, 5};
    
    // ===== std::array(推荐)=====
    array<int, 5> cpp_arr = {10, 20, 30, 40, 50};
    cpp_arr[0] = 100;
    cout << "std::array大小: " << cpp_arr.size() << endl;
    
    // ===== std::vector(动态数组)=====
    vector<int> vec = {1, 2, 3};
    vec.push_back(4);        // 添加元素
    vec.push_back(5);
    
    cout << "Vector元素: ";
    for (int num : vec) {    // 范围for循环(C++11)
        cout << num << " ";
    }
    cout << endl;
    
    // ===== C++字符串 ====
    string str1 = "Ubuntu";
    string str2 = " Linux";
    string str3 = str1 + str2;  // 直接拼接
    
    cout << "字符串: " << str3 << endl;
    cout << "长度: " << str3.length() << endl;
    
    // 字符串查找
    size_t pos = str3.find("Linux");
    if (pos != string::npos) {
        cout << "找到Linux在位置: " << pos << endl;
    }
    
    return 0;
}

18.1.6 指针

c 复制代码
#include <stdio.h>
#include <stdlib.h>  // malloc/free

int main() {
    int var = 100;
    
    // ===== 指针基础 =====
    int *ptr;           // 声明指针(指向int的指针)
    ptr = &var;         // &取地址,ptr现在存储var的地址
    
    printf("var值: %d\n", var);
    printf("var地址: %p\n", &var);
    printf("ptr值(地址): %p\n", ptr);
    printf("ptr指向的值: %d\n", *ptr);  // *解引用
    
    // ===== 通过指针修改变量 =====
    *ptr = 200;         // 通过指针修改var的值
    printf("修改后var值: %d\n", var);
    
    // ===== 指针的大小 =====
    printf("指针大小: %zu 字节\n", sizeof(ptr));  // 64位系统为8字节
    
    // ===== 指针运算 =====
    int arr[5] = {10, 20, 30, 40, 50};
    int *arr_ptr = arr;  // 数组名即地址
    
    printf("\n数组指针:\n");
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d, 地址: %p\n", i, *(arr_ptr + i), arr_ptr + i);
        // arr_ptr + i 等价于 &arr[i]
    }
    
    // ===== 指针与数组 =====
    printf("\n指针遍历数组:\n");
    for (int i = 0; i < 5; i++) {
        printf("*(arr + %d) = %d\n", i, *(arr + i));
    }
    
    // ===== 动态内存分配 =====
    int *dynamic_ptr = (int*)malloc(5 * sizeof(int));  // 分配5个int空间
    
    if (dynamic_ptr == NULL) {
        printf("内存分配失败\n");
        return 1;
    }
    
    // 使用动态内存
    for (int i = 0; i < 5; i++) {
        dynamic_ptr[i] = i * 100;  // 可用数组方式访问
    }
    
    printf("\n动态数组:\n");
    for (int i = 0; i < 5; i++) {
        printf("dynamic_ptr[%d] = %d\n", i, dynamic_ptr[i]);
    }
    
    // 释放内存
    free(dynamic_ptr);
    dynamic_ptr = NULL;  // 避免悬空指针
    
    // ===== 指针数组(字符串数组)=====
    const char *names[] = {"Ubuntu", "Linux", "C Programming"};
    printf("\n字符串数组:\n");
    for (int i = 0; i < 3; i++) {
        printf("names[%d] = %s\n", i, names[i]);
    }
    
    // ===== 多级指针 =====
    int value = 500;
    int *p1 = &value;
    int **p2 = &p1;  // 指向指针的指针
    
    printf("\n多级指针:\n");
    printf("value: %d\n", value);
    printf("*p1: %d\n", *p1);
    printf("**p2: %d\n", **p2);
    
    // ===== void指针 =====
    void *void_ptr = &var;  // 可以指向任何类型
    printf("\nvoid指针指向的值: %d\n", *(int*)void_ptr);  // 需强制转换
    
    // ===== 函数指针 =====
    int add(int x, int y) {
        return x + y;
    }
    
    int (*func_ptr)(int, int) = add;  // 声明函数指针
    printf("\n函数指针调用: %d + %d = %d\n", 5, 3, func_ptr(5, 3));
    
    return 0;
}

C++指针示例:

cpp 复制代码
#include <iostream>
#include <memory>  // 智能指针

using namespace std;

int main() {
    int *ptr = new int(100);        // C++动态分配
    cout << "动态分配值: " << *ptr << endl;
    
    delete ptr;                     // 释放内存
    ptr = nullptr;                  // 空指针
    
    // ===== 智能指针(推荐)=====
    // unique_ptr:独占所有权
    unique_ptr<int> uniq_ptr(new int(200));
    cout << "unique_ptr: " << *uniq_ptr << endl;
    
    // shared_ptr:共享所有权
    shared_ptr<int> shared1 = make_shared<int>(300);
    shared_ptr<int> shared2 = shared1;
    cout << "shared_ptr引用计数: " << shared1.use_count() << endl;
    
    // weak_ptr:弱引用
    weak_ptr<int> weak = shared1;
    
    return 0;
}

18.1.7 函数

c 复制代码
#include <stdio.h>
#include <stdlib.h>

// ===== 函数参数传递方式 =====

// 1. 值传递(默认)
void swap_by_value(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    // 只修改局部副本,不影响原始变量
}

// 2. 指针传递(模拟引用)
void swap_by_pointer(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    // 通过指针修改原始变量
}

// 3. 数组作为参数(退化为指针)
void print_array(int arr[], int size) {
    // 等价于 void print_array(int *arr, int size)
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// ===== 函数返回值 =====

// 返回简单值
int add(int a, int b) {
    return a + b;
}

// 返回指针(动态内存)
int *create_array(int size) {
    int *arr = (int*)malloc(size * sizeof(int));
    if (arr) {
        for (int i = 0; i < size; i++) {
            arr[i] = i;
        }
    }
    return arr;
}

// 返回结构体
struct Point {
    int x;
    int y;
};

struct Point create_point(int x, int y) {
    struct Point p = {x, y};
    return p;
}

// ===== 可变参数函数 =====
#include <stdarg.h>

int sum(int count, ...) {
    va_list args;
    va_start(args, count);
    
    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);
    }
    
    va_end(args);
    return total;
}

// ===== 递归函数 =====
int factorial(int n) {
    if (n <= 1) {
        return 1;
    }
    return n * factorial(n - 1);
}

// ===== 静态函数(文件作用域)=====
static void helper_function() {
    // 只在当前文件可见
    printf("这是静态辅助函数\n");
}

// ===== 内联函数(C99)=====
inline int max(int a, int b) {
    return (a > b) ? a : b;
}

int main() {
    int x = 5, y = 10;
    
    // 值传递
    printf("交换前: x=%d, y=%d\n", x, y);
    swap_by_value(x, y);
    printf("值传递交换后: x=%d, y=%d(未变)\n", x, y);
    
    // 指针传递
    swap_by_pointer(&x, &y);
    printf("指针传递交换后: x=%d, y=%d(已交换)\n", x, y);
    
    // 数组参数
    int arr[5] = {1, 2, 3, 4, 5};
    printf("数组: ");
    print_array(arr, 5);
    
    // 动态数组
    int *dynamic_arr = create_array(5);
    if (dynamic_arr) {
        printf("动态数组: ");
        print_array(dynamic_arr, 5);
        free(dynamic_arr);
    }
    
    // 可变参数
    printf("可变参数求和: %d\n", sum(4, 1, 2, 3, 4));
    
    // 递归
    printf("5的阶乘: %d\n", factorial(5));
    
    // 返回结构体
    struct Point p = create_point(10, 20);
    printf("点坐标: (%d, %d)\n", p.x, p.y);
    
    return 0;
}

C++函数特性:

cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

// ===== 函数重载 =====
int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

// ===== 默认参数 =====
void print_info(string name, int age = 25, string city = "Beijing") {
    cout << "姓名: " << name << ", 年龄: " << age << ", 城市: " << city << endl;
}

// ===== 引用参数 =====
void swap_by_ref(int &a, int &b) {  // 引用传递
    int temp = a;
    a = b;
    b = temp;
}

// ===== 函数模板 =====
template <typename T>
T max_val(T a, T b) {
    return (a > b) ? a : b;
}

// ===== Lambda表达式(C++11)=====
auto lambda_add = [](int a, int b) -> int {
    return a + b;
};

int main() {
    // 函数重载
    cout << "add(5, 3) = " << add(5, 3) << endl;
    cout << "add(5.5, 3.3) = " << add(5.5, 3.3) << endl;
    
    // 默认参数
    print_info("张三");
    print_info("李四", 30);
    print_info("王五", 35, "上海");
    
    // 引用传递
    int x = 10, y = 20;
    swap_by_ref(x, y);
    cout << "引用交换后: " << x << ", " << y << endl;
    
    // 函数模板
    cout << "max(10, 20): " << max_val(10, 20) << endl;
    cout << "max(3.14, 2.71): " << max_val(3.14, 2.71) << endl;
    
    // Lambda
    cout << "Lambda: " << lambda_add(5, 7) << endl;
    
    // 使用STL算法和Lambda
    vector<int> vec = {5, 2, 8, 1, 9};
    sort(vec.begin(), vec.end());  // 默认升序
    
    for_each(vec.begin(), vec.end(), [](int n) {
        cout << n << " ";
    });
    cout << endl;
    
    return 0;
}

18.1.8 结构体、联合体和枚举

c 复制代码
#include <stdio.h>
#include <string.h>

// ===== 结构体 =====
struct Student {
    char name[50];
    int age;
    float gpa;
    char major[30];
};

// 嵌套结构体
struct Date {
    int year;
    int month;
    int day;
};

struct Employee {
    int id;
    char name[50];
    struct Date hire_date;
};

// ===== 联合体 =====
union Data {
    int i;
    float f;
    char str[20];
};

// ===== 枚举 =====
enum Priority {
    LOW = 1,
    MEDIUM,
    HIGH,
    CRITICAL
};

// 带值的枚举
enum ErrorCode {
    SUCCESS = 0,
    ERROR_INVALID = 1001,
    ERROR_NOT_FOUND = 1002,
    ERROR_TIMEOUT = 1003
};

// ===== 位域(结构体成员)=====
struct Flags {
    unsigned int is_active : 1;    // 1位
    unsigned int is_admin : 1;     // 1位
    unsigned int status : 3;       // 3位,0-7
    unsigned int : 2;              // 保留2位
    unsigned int priority : 3;     // 3位
};

int main() {
    // ===== 结构体使用 =====
    struct Student stu1;
    strcpy(stu1.name, "Alice");
    stu1.age = 20;
    stu1.gpa = 3.8;
    strcpy(stu1.major, "Computer Science");
    
    printf("学生信息:\n");
    printf("姓名: %s\n", stu1.name);
    printf("年龄: %d\n", stu1.age);
    printf("GPA: %.2f\n", stu1.gpa);
    printf("专业: %s\n", stu1.major);
    
    // 结构体初始化(C99)
    struct Student stu2 = {
        .name = "Bob",
        .age = 22,
        .gpa = 3.9,
        .major = "Mathematics"
    };
    
    // 结构体数组
    struct Student class[3] = {
        {"Alice", 20, 3.8, "CS"},
        {"Bob", 22, 3.9, "Math"},
        {"Charlie", 19, 3.7, "Physics"}
    };
    
    printf("\n班级学生:\n");
    for (int i = 0; i < 3; i++) {
        printf("%d. %s - %s\n", i+1, class[i].name, class[i].major);
    }
    
    // 嵌套结构体
    struct Employee emp1 = {
        .id = 1001,
        .name = "David",
        .hire_date = {2023, 6, 15}
    };
    
    printf("\n员工: %s\n", emp1.name);
    printf("入职日期: %04d-%02d-%02d\n", emp1.hire_date.year, emp1.hire_date.month, emp1.hire_date.day);
    
    // ===== 联合体使用 =====
    union Data data;
    
    data.i = 10;
    printf("\n联合体int: %d\n", data.i);
    
    data.f = 3.14f;
    printf("联合体float: %.2f\n", data.f);
    // 注意:此时data.i的值已损坏
    
    strcpy(data.str, "Hello");
    printf("联合体string: %s\n", data.str);
    // 此时data.f也损坏
    
    // ===== 枚举使用 =====
    enum Priority task_priority = HIGH;
    printf("\n任务优先级: %d\n", task_priority);  // 输出3
    
    switch (task_priority) {
        case LOW:
            printf("优先级: 低\n");
            break;
        case MEDIUM:
            printf("优先级: 中\n");
            break;
        case HIGH:
            printf("优先级: 高\n");
            break;
        case CRITICAL:
            printf("优先级: 关键\n");
            break;
    }
    
    // ===== 位域使用 =====
    struct Flags flags = {0};
    flags.is_active = 1;
    flags.is_admin = 0;
    flags.status = 5;
    flags.priority = HIGH;
    
    printf("\n标志位:\n");
    printf("激活: %d\n", flags.is_active);
    printf("管理员: %d\n", flags.is_admin);
    printf("状态: %d\n", flags.status);
    printf("优先级: %d\n", flags.priority);
    printf("结构体大小: %zu 字节\n", sizeof(flags));  // 仅1字节
    
    // ===== 类型定义 =====
    typedef struct Student Student;  // 简化结构体类型名
    Student stu3 = {"Eve", 21, 3.95, "Engineering"};
    
    typedef enum Priority Priority;  // 简化枚举类型名
    Priority p = CRITICAL;
    
    return 0;
}

C++扩展:

cpp 复制代码
#include <iostream>
#include <string>
#include <variant>  // C++17 改进的联合体
#include <optional> // C++17 可选值

using namespace std;

// ===== C++结构体(类)=====
struct Point {
    int x;
    int y;
    
    // 成员函数
    void print() const {
        cout << "(" << x << ", " << y << ")" << endl;
    }
};

// ===== 枚举类(推荐)=====
enum class Color {
    Red,
    Green,
    Blue
};

// ===== 强类型枚举 =====
enum class Status : int {
    Success = 0,
    Error = 1,
    Pending = 2
};

// ===== std::variant(类型安全的联合体)=====
using MyVariant = variant<int, float, string>;

// ===== std::optional(可选值)=====
optional<int> divide(int a, int b) {
    if (b == 0) return nullopt;
    return a / b;
}

int main() {
    // C++结构体
    Point p = {10, 20};
    p.print();
    
    // 枚举类
    Color c = Color::Red;
    // cout << c; // 错误!需要转换
    cout << "Color值: " << static_cast<int>(c) << endl;
    
    // variant
    MyVariant v = 42;
    cout << "variant holds int: " << get<int>(v) << endl;
    
    v = 3.14f;
    cout << "variant holds float: " << get<float>(v) << endl;
    
    // optional
    auto result = divide(10, 2);
    if (result) {
        cout << "10/2 = " << result.value() << endl;
    }
    
    auto result2 = divide(10, 0);
    if (!result2) {
        cout << "除零错误" << endl;
    }
    
    return 0;
}

18.2 GUI编程基础

18.2.1 GUI发展概述

GUI(图形用户界面)发展历程:

  1. X Window System (1984):Unix/Linux图形基础
  2. GTK (1997):GIMP工具包,C语言编写,GNOME桌面基础
  3. Qt (1995):C++框架,跨平台,KDE桌面基础
  4. 现代趋势:QML/Qt Quick、GTK4、Wayland

18.2.2 GDK简介

GDK(GIMP Drawing Kit)是GTK的底层图形库,封装了X11/Wayland。

c 复制代码
// 简单GDK程序(需安装GTK开发包)
#include <gdk/gdk.h>

int main(int argc, char *argv[]) {
    // GDK在GTK程序中自动初始化
    // 直接使用较少,通常通过GTK+调用
    return 0;
}

18.3 基于Qt的图形用户界面编程

18.3.1 Qt简介

Qt是跨平台的C++应用框架,特点:

  • 信号与槽机制
  • 丰富的GUI组件
  • 跨平台(Linux/Windows/macOS)
  • QML现代UI设计

18.3.2 Qt安装

方法1:通过APT安装(推荐,Ubuntu 22.04)

bash 复制代码
# 更新软件源
sudo apt update

# 安装Qt5开发包
sudo apt install qt5-default qtbase5-dev qtchooser -y

# 安装Qt Creator IDE
sudo apt install qtcreator -y

# 安装常用模块
sudo apt install qtmultimedia5-dev \
                 libqt5network5 \
                 libqt5sql5 \
                 libqt5widgets5 \
                 libqt5gui5 \
                 libqt5core5a -y

# 验证安装
qmake --version    # 查看Qt版本
which qtcreator    # 查看Qt Creator路径

# 启动Qt Creator
qtcreator &

方法2:在线安装(获取最新版Qt 6)

bash 复制代码
# 1. 下载在线安装器
wget https://download.qt.io/official_releases/online_installers/qt-unified-linux-x64-online.run

# 2. 添加执行权限
chmod +x qt-unified-linux-x64-online.run

# 3. 运行安装器(图形界面)
./qt-unified-linux-x64-online.run

# 4. 登录Qt账号(没有需注册)
# 5. 选择安装组件:
#    - Desktop gcc 64-bit
#    - Qt Creator
#    - Qt 6.x Core/Gui/Widgets
#    - 其他需要的模块

# 6. 接受协议并等待下载安装(约2-5GB)

# 7. 安装完成后配置环境变量
echo 'export PATH=/home/yourname/Qt/6.x.x/gcc_64/bin:$PATH' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=/home/yourname/Qt/6.x.x/gcc_64/lib:$LD_LIBRARY_PATH' << ~/.bashrc
source ~/.bashrc

方法3:安装Qt6(Ubuntu 22.10+,或添加PPA)

bash 复制代码
# 添加Qt6 PPA(Ubuntu 22.04需此步骤)
sudo add-apt-repository ppa:beineri/opt-qt-6.2.4-jammy
sudo apt update
sudo apt install qt6-base-dev qt6-base-dev-tools -y

18.3.3 Qt Creator使用

Qt Creator IDE配置步骤:

  1. 首次启动配置
bash 复制代码
# 启动Qt Creator
qtcreator

# 配置Kit(工具链)
# 1. 打开 "编辑" -> "Preferences"(或"选项")
# 2. 选择 "Kits" -> "Qt Versions"
#    - 点击"添加",选择qmake路径(/usr/bin/qmake或~/Qt/6.x.x/gcc_64/bin/qmake)
# 3. 选择 "编译器"
#    - 应自动检测到GCC/G++
# 4. 选择 "Kits"
#    - 点击"添加",选择Qt版本和编译器
#    - 名称:Desktop Qt 5.15.3 GCC 64bit
  1. 创建第一个Qt项目
bash 复制代码
# 在Qt Creator中:
# 1. 点击 "New Project"(新建项目)
# 2. 选择 "Application" -> "Qt Widgets Application"
# 3. 点击 "Choose"
# 4. 设置项目名称和路径(如:/home/user/QtProjects/HelloQt)
# 5. 选择构建系统:qmake(简单项目)或CMake(复杂项目)
# 6. 选择基类:QMainWindow(主窗口)、QWidget(通用)、QDialog(对话框)
# 7. 点击"下一步"完成创建
  1. Qt项目结构说明

    HelloQt/
    ├── HelloQt.pro # qmake项目文件
    ├── main.cpp # 主函数入口
    ├── mainwindow.cpp # 主窗口实现
    ├── mainwindow.h # 主窗口头文件
    ├── mainwindow.ui # UI界面文件(XML格式)
    └── resources.qrc # 资源文件(可选)

18.3.4 关键概念:信号与槽

信号与槽是Qt的核心机制,用于对象间通信。

示例1:基础信号槽

cpp 复制代码
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT  // 必须包含的宏,支持信号槽

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:  // 槽函数声明
    void onButtonClicked();      // 按钮点击槽
    void onSliderValueChanged(int value);  // 滑块变化槽

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H
cpp 复制代码
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>
#include <QSlider>
#include <QLabel>
#include <QVBoxLayout>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    
    // 创建中心部件
    QWidget *centralWidget = new QWidget(this);
    setCentralWidget(centralWidget);
    
    // 创建布局
    QVBoxLayout *layout = new QVBoxLayout(centralWidget);
    
    // 创建按钮
    QPushButton *button = new QPushButton("点击我", this);
    layout->addWidget(button);
    
    // 创建滑块
    QSlider *slider = new QSlider(Qt::Horizontal, this);
    slider->setRange(0, 100);
    layout->addWidget(slider);
    
    // 创建标签
    QLabel *label = new QLabel("当前值: 0", this);
    layout->addWidget(label);
    
    // ===== 连接信号与槽 =====
    
    // 方式1:Qt5新语法(编译期检查,推荐)
    connect(button, &QPushButton::clicked,
            this, &MainWindow::onButtonClicked);
    
    // 方式2:Qt4旧语法(运行时检查,仍可用)
    connect(slider, SIGNAL(valueChanged(int)),
            label, SLOT(setNum(int)));  // 直接连接到QLabel的槽
    
    // 方式3:Lambda表达式(C++11,推荐)
    connect(slider, &QSlider::valueChanged,
            [label](int value){
                label->setText(QString("当前值: %1").arg(value));
            });
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::onButtonClicked()
{
    // 槽函数实现
    QMessageBox::information(this, "消息", "按钮被点击了!");
}

void MainWindow::onSliderValueChanged(int value)
{
    // 处理滑块值变化
    qDebug() << "滑块值:" << value;
}

示例2:自定义信号

cpp 复制代码
// mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>
#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT

public:
    explicit MyThread(QObject *parent = nullptr);
    
signals:  // 信号声明(无需实现)
    void progressChanged(int percent);
    void workFinished();

protected:
    void run() override;  // 线程入口

private:
    volatile bool m_stop;  // 停止标志
};

#endif

// mythread.cpp
#include "mythread.h"
#include <QDebug>

MyThread::MyThread(QObject *parent) : QThread(parent), m_stop(false)
{
}

void MyThread::run()
{
    for (int i = 0; i <= 100 && !m_stop; i++) {
        // 模拟工作
        msleep(50);
        
        // 发射信号
        emit progressChanged(i);  // 通知进度
    }
    
    emit workFinished();  // 通知完成
}

// 在主窗口中使用
void MainWindow::startWork()
{
    MyThread *thread = new MyThread(this);
    
    // 连接线程信号到槽
    connect(thread, &MyThread::progressChanged,
            ui->progressBar, &QProgressBar::setValue);
    
    connect(thread, &MyThread::workFinished,
            [thread](){
                qDebug() << "工作完成";
                thread->deleteLater();  // 延迟删除
            });
    
    thread->start();  // 启动线程
}

18.3.5 Qt项目完整示例

项目:简易计算器

cpp 复制代码
// calculator.pro (qmake项目文件)
QT       += core gui widgets

CONFIG   += c++17

SOURCES  += main.cpp \
            calculator.cpp

HEADERS  += calculator.h

# 设计UI时取消下面注释
# FORMS    += calculator.ui
cpp 复制代码
// main.cpp
#include "calculator.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    // 创建QApplication对象(管理GUI应用程序)
    QApplication app(argc, argv);
    
    // 创建主窗口
    Calculator calc;
    calc.setWindowTitle("简易计算器");
    calc.resize(300, 200);
    calc.show();
    
    // 进入事件循环
    return app.exec();
}
cpp 复制代码
// calculator.h
#ifndef CALCULATOR_H
#define CALCULATOR_H

#include <QWidget>
#include <QLineEdit>
#include <QPushButton>
#include <QGridLayout>

class Calculator : public QWidget
{
    Q_OBJECT

public:
    Calculator(QWidget *parent = nullptr);

private slots:
    void onDigitClicked();      // 数字键
    void onOperatorClicked();   // 运算符
    void onEqualClicked();      // 等于
    void onClearClicked();      // 清除

private:
    QLineEdit *display;         // 显示框
    double currentValue;        // 当前值
    double previousValue;       // 前一个值
    QString pendingOperator;    // 待执行运算符
    bool waitingForOperand;     // 是否在等待操作数
    
    void createButtons();       // 创建按钮
    void calculate();           // 执行计算
};

#endif
cpp 复制代码
// calculator.cpp
#include "calculator.h"
#include <QDebug>

Calculator::Calculator(QWidget *parent)
    : QWidget(parent), currentValue(0), previousValue(0), waitingForOperand(true)
{
    // 创建显示框
    display = new QLineEdit("0", this);
    display->setReadOnly(true);
    display->setAlignment(Qt::AlignRight);
    display->setMaxLength(15);
    
    // 创建布局
    QGridLayout *mainLayout = new QGridLayout(this);
    mainLayout->setSizeConstraint(QLayout::SetFixedSize);
    mainLayout->addWidget(display, 0, 0, 1, 4);
    
    // 创建按钮
    createButtons();
    
    setLayout(mainLayout);
}

void Calculator::createButtons()
{
    const char *buttons[16] = {
        "7", "8", "9", "/",
        "4", "5", "6", "*",
        "1", "2", "3", "-",
        "0", "C", "=", "+"
    };
    
    QGridLayout *layout = qobject_cast<QGridLayout*>(this->layout());
    
    for (int i = 0; i < 16; i++) {
        QPushButton *btn = new QPushButton(buttons[i], this);
        int row = 1 + i / 4;
        int col = i % 4;
        layout->addWidget(btn, row, col);
        
        // 连接信号
        if (buttons[i][0] >= '0' && buttons[i][0] <= '9') {
            connect(btn, &QPushButton::clicked, this, &Calculator::onDigitClicked);
        } else if (buttons[i][0] == '=') {
            connect(btn, &QPushButton::clicked, this, &Calculator::onEqualClicked);
        } else if (buttons[i][0] == 'C') {
            connect(btn, &QPushButton::clicked, this, &Calculator::onClearClicked);
        } else {
            connect(btn, &QPushButton::clicked, this, &Calculator::onOperatorClicked);
        }
    }
}

void Calculator::onDigitClicked()
{
    QPushButton *clickedBtn = qobject_cast<QPushButton*>(sender());
    QString digit = clickedBtn->text();
    
    if (waitingForOperand) {
        display->setText(digit);
        waitingForOperand = false;
    } else {
        display->setText(display->text() + digit);
    }
}

void Calculator::onOperatorClicked()
{
    QPushButton *clickedBtn = qobject_cast<QPushButton*>(sender());
    pendingOperator = clickedBtn->text();
    
    previousValue = display->text().toDouble();
    waitingForOperand = true;
}

void Calculator::onEqualClicked()
{
    calculate();
    pendingOperator.clear();
    waitingForOperand = true;
}

void Calculator::onClearClicked()
{
    display->setText("0");
    currentValue = 0;
    previousValue = 0;
    pendingOperator.clear();
    waitingForOperand = true;
}

void Calculator::calculate()
{
    if (!pendingOperator.isEmpty()) {
        currentValue = display->text().toDouble();
        
        if (pendingOperator == "+") {
            currentValue = previousValue + currentValue;
        } else if (pendingOperator == "-") {
            currentValue = previousValue - currentValue;
        } else if (pendingOperator == "*") {
            currentValue = previousValue * currentValue;
        } else if (pendingOperator == "/") {
            if (currentValue != 0) {
                currentValue = previousValue / currentValue;
            } else {
                display->setText("Error");
                return;
            }
        }
        
        display->setText(QString::number(currentValue));
    }
}

编译与运行Qt项目:

bash 复制代码
# 在项目目录中
mkdir build && cd build

# qmake生成Makefile
qmake ../calculator.pro

# make编译
make -j4

# 运行
./calculator

# 或使用Qt Creator直接构建运行
# 点击左下角绿色三角形"运行"按钮

18.4 Linux编程基础

18.4.1 源程序编辑器

1. Vim编辑器

bash 复制代码
# 安装Vim
sudo apt install vim -y

# 配置Vim C/C++开发环境
# 创建配置文件
cat > ~/.vimrc << 'EOF'
" Vim配置文件 - C/C++开发

" 基本设置
set number              " 显示行号
set tabstop=4           " Tab宽度4
set shiftwidth=4        " 自动缩进宽度4
set expandtab           " 使用空格代替Tab
set autoindent          " 自动缩进
set smartindent         " 智能缩进
syntax enable           " 启用语法高亮
set showmatch           " 显示匹配的括号

" C/C++特定设置
autocmd FileType c,cpp set cindent
autocmd FileType c,cpp set cinoptions={0,1s,t0,n-2,p2s,(03s,=.5s,>1s,=1s,:1s
autocmd FileType c,cpp set formatoptions-=ro

" 启用Omni补全
autocmd FileType c set omnifunc=ccomplete#Complete
autocmd FileType cpp set omnifunc=cppcomplete#Complete

" 快捷键映射
autocmd FileType c,cpp map <F5> :!gcc % -o %< && ./%< <CR>
autocmd FileType cpp map <F5> :!g++ -std=c++17 % -o %< && ./%< <CR>

" 显示函数列表
set tagstack
EOF

# 安装插件管理器(可选)
curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

# 在~/.vimrc中添加插件
cat >> ~/.vimrc << 'EOF'
" 插件管理
call plug#begin('~/.vim/plugged')

Plug 'vim-syntastic/syntastic'      " 语法检查
Plug 'rdnetto/YCM-Generator'        " YouCompleteMe配置生成
Plug 'ycm-core/YouCompleteMe'       " 代码补全

" 安装后运行: cd ~/.vim/plugged/YouCompleteMe && python3 install.py --clangd-completer
" 需要CMake和clangd: sudo apt install cmake clangd

call plug#end()
EOF

2. VS Code编辑器

bash 复制代码
# 安装VS Code(如前面所述)
# 配置C/C++扩展
# 创建项目配置文件

# 在项目根目录创建.vscode目录
mkdir -p .vscode

# 创建c_cpp_properties.json(配置编译器路径和包含路径)
cat > .vscode/c_cpp_properties.json << 'EOF'
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/usr/include/**",
                "/usr/local/include/**"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c17",
            "cppStandard": "c++17",
            "intelliSenseMode": "linux-gcc-x64",
            "browse": {
                "path": [
                    "${workspaceFolder}",
                    "/usr/include"
                ]
            }
        }
    ],
    "version": 4
}
EOF

# 创建tasks.json(构建任务)
cat > .vscode/tasks.json << 'EOF'
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "build C++",
            "type": "shell",
            "command": "g++",
            "args": [
                "-std=c++17",
                "-g",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoPrefix}",
                "-Wall",
                "-Wextra"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "presentation": {
                "echo": true,
                "reveal": "always",
                "focus": false,
                "panel": "shared"
            },
            "problemMatcher": "$gcc"
        }
    ]
}
EOF

# 创建launch.json(调试配置)
cat > .vscode/launch.json << 'EOF'
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "C/C++ Debug",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}/${fileBasenameNoPrefix}",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "build C++",
            "miDebuggerPath": "/usr/bin/gdb"
        }
    ]
}
EOF

18.4.2 GCC编译器

GCC编译流程详解:

bash 复制代码
# 源代码: hello.c
cat > hello.c << 'EOF'
#include <stdio.h>

#define VERSION "1.0"

int main() {
    printf("Hello Ubuntu 22.04!\n");
    printf("Version: %s\n", VERSION);
    return 0;
}
EOF

# ===== 1. 预处理(-E)=====
# 处理#include、#define等预处理指令
gcc -E hello.c -o hello.i
# 查看预处理结果
head -50 hello.i

# ===== 2. 编译(-S)=====
# 将预处理后的文件编译为汇编代码
gcc -S hello.i -o hello.s
# 查看汇编代码
cat hello.s

# ===== 3. 汇编(-c)=====
# 将汇编代码转为目标文件(二进制)
gcc -c hello.s -o hello.o
# 查看目标文件信息
file hello.o
nm hello.o  # 查看符号表

# ===== 4. 链接(默认)=====
# 将目标文件链接为可执行文件
gcc hello.o -o hello
# 查看可执行文件
file hello
ldd hello   # 查看依赖库

# ===== 一步编译(常用)=====
gcc hello.c -o hello

# ===== 常用编译选项 =====
# -Wall:启用所有警告
# -Wextra:额外警告
# -g:生成调试信息
# -O2:优化级别2
# -std=c11:C标准
# -I:添加头文件搜索路径
# -L:添加库搜索路径
# -l:链接库(-lm链接数学库)

# 编译多文件项目
# main.c
cat > main.c << 'EOF'
#include "math_utils.h"

int main() {
    int result = add(10, 20);
    printf("10 + 20 = %d\n", result);
    return 0;
}
EOF

# math_utils.h
cat > math_utils.h << 'EOF'
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int add(int a, int b);
int subtract(int a, int b);

#endif
EOF

# math_utils.c
cat > math_utils.c << 'EOF'
#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}
EOF

# 编译命令
gcc -c main.c -o main.o
gcc -c math_utils.c -o math_utils.o
gcc main.o math_utils.o -o myapp

# 或使用通配符
gcc *.c -o myapp

C++编译示例:

bash 复制代码
# C++源文件: main.cpp
cat > main.cpp << 'EOF'
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9};
    std::sort(vec.begin(), vec.end());
    
    for (int x : vec) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
    
    return 0;
}
EOF

# 编译C++(使用g++)
g++ -std=c++17 -Wall -O2 -o myapp main.cpp

# 使用CMake编译(现代C++项目)
cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.16)
project(MyApp VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# 查找Qt5
find_package(Qt5 COMPONENTS Widgets REQUIRED)

# 添加可执行文件
add_executable(myapp main.cpp)

# 链接库
target_link_libraries(myapp Qt5::Widgets)
EOF

# 构建
mkdir build && cd build
cmake ..
make -j4

18.4.3 GDB调试器

GDB使用教程:

bash 复制代码
# 编译带调试信息的程序
cat > debug_demo.c << 'EOF'
#include <stdio.h>
#include <stdlib.h>

int factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int main(int argc, char *argv[]) {
    int *ptr = NULL;
    int num = 10;
    
    // 计算阶乘
    int result = factorial(num);
    printf("%d! = %d\n", num, result);
    
    // 段错误示例
    if (argc > 1) {
        *ptr = 100;  // 空指针解引用
    }
    
    return 0;
}
EOF

# 编译(必须加-g)
gcc -g -o debug_demo debug_demo.c

# ===== GDB常用命令 =====

# 启动GDB
gdb ./debug_demo

# 在GDB中:
# (gdb) help                    # 显示帮助
# (gdb) list                    # 显示源代码
# (gdb) list 20                 # 显示第20行附近
# (gdb) break main              # 在main函数设置断点
# (gdb) break 15                # 在第15行设置断点
# (gdb) break factorial         # 在factorial函数设置断点
# (gdb) info breakpoints        # 查看断点
# (gdb) delete 1                # 删除断点1

# (gdb) run                     # 运行程序
# (gdb) run arg1                # 带参数运行

# (gdb) next (n)                # 单步执行(不进入函数)
# (gdb) step (s)                # 单步执行(进入函数)
# (gdb) continue (c)            # 继续执行
# (gdb) finish                  # 执行到函数返回

# (gdb) print num               # 打印变量值
# (gdb) print *ptr              # 打印指针指向的值
# (gdb) print result            # 打印变量
# (gdb) display num             # 每次停止时自动显示

# (gdb) backtrace (bt)          # 查看调用栈
# (gdb) frame 1                 # 切换到栈帧1
# (gdb) info locals             # 查看局部变量
# (gdb) info args               # 查看函数参数

# (gdb) watch ptr               # 监视变量变化
# (gdb) rwatch ptr              # 读监视
# (gdb) awatch ptr              # 读写监视

# (gdb) set var num = 20        # 修改变量值
# (gdb) set var ptr = (int*)malloc(4)  # 动态分配内存

# (gdb) quit                    # 退出GDB

GDB调试脚本示例:

bash 复制代码
# 创建GDB脚本
cat > debug_commands.txt << 'EOF'
# GDB命令脚本
file debug_demo
break main
run
print num
set var num = 5
print num
continue
quit
EOF

# 执行GDB脚本
gdb -x debug_commands.txt

# 或者
gdb ./debug_demo < debug_commands.txt

TUI模式(终端图形界面)

bash 复制代码
# 启动TUI模式
gdb -tui ./debug_demo

# 在GDB中切换TUI
# Ctrl+X A: 切换TUI模式
# Ctrl+X 2: 切换到源码+汇编+命令三视图

GDB附加到运行进程

bash 复制代码
# 1. 查找进程ID
ps aux | grep myapp

# 2. 附加调试
gdb -p <PID>

# 3. 设置断点并继续
(gdb) break some_function
(gdb) continue

18.4.4 make工具和Makefile

make工具安装:

bash 复制代码
# 安装make
sudo apt install make -y

# 验证
make --version  # GNU Make 4.x

Makefile基础示例:

bash 复制代码
# Makefile基础结构
# 目标: 依赖
# <Tab>命令

# 创建项目文件
mkdir -p src include

# 头文件 include/math_utils.h
cat > include/math_utils.h << 'EOF'
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);

#endif
EOF

# 源文件 src/math_utils.c
cat > src/math_utils.c << 'EOF'
#include "math_utils.h"

int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
double divide(int a, int b) { 
    if (b != 0) return (double)a / b;
    return 0; 
}
EOF

# 源文件 src/main.c
cat > src/main.c << 'EOF'
#include <stdio.h>
#include "math_utils.h"  # 注意:此处应为 #include "math_utils.h",但原始代码使用的是 #,这可能是一个错误。应该是 #include "math_utils.h"。我将进行修正。

int main() {
    printf("10 + 5 = %d\n", add(10, 5));
    printf("10 - 5 = %d\n", subtract(10, 5));
    printf("10 * 5 = %d\n", multiply(10, 5));
    printf("10 / 5 = %.2f\n", divide(10, 5));
    return 0;
}
EOF

# ===== 基础Makefile =====
cat > Makefile << 'EOF'
# 编译器
CC = gcc
CXX = g++

# 编译选项
CFLAGS = -Wall -Wextra -g -O2 -I./include
CXXFLAGS = -std=c++17 $(CFLAGS)

# 链接选项
LDFLAGS = -lm

# 目标可执行文件
TARGET = math_app

# 源文件(通配符)
SRCS = $(wildcard src/*.c)
OBJS = $(SRCS:.c=.o)  # 目标文件列表

# 默认目标
all: $(TARGET)

# 链接规则
$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) $^ -o $@

# 编译规则(.c -> .o)
src/%.o: src/%.c
	$(CC) $(CFLAGS) -c $< -o $@

# 清理
clean:
	rm -f src/*.o $(TARGET)

# 伪目标(不生成文件)
.PHONY: all clean

# 安装
install: $(TARGET)
	sudo cp $(TARGET) /usr/local/bin/

# 卸载
uninstall:
	sudo rm -f /usr/local/bin/$(TARGET)

# 调试版本
debug: CFLAGS += -DDEBUG -g
debug: clean $(TARGET)

# 发布版本
release: CFLAGS += -DNDEBUG -O3
release: clean $(TARGET)

# 帮助
help:
	@echo "可用目标:"
	@echo "  all       - 编译默认版本"
	@echo "  clean     - 清理生成的文件"
	@echo "  install   - 安装到/usr/local/bin"
	@echo "  uninstall - 卸载"
	@echo "  debug     - 编译调试版本"
	@echo "  release   - 编译发布版本"
EOF

# ===== 使用Makefile =====

# 编译
make
# 或 make all

# 清理
make clean

# 安装
sudo make install

# 卸载
sudo make uninstall

# 编译调试版本
make debug

# 查看依赖关系
make -n          # 仅显示命令不执行
make --dry-run   # 同上

# 并行编译
make -j4         # 4个线程

# 查看make版本
make --version

高级Makefile示例(包含自动依赖生成):

bash 复制代码
cat > Makefile.advanced << 'EOF'
# 高级Makefile - 自动依赖生成

# 编译器
CC = gcc
CXX = g++

# 目录
SRCDIR = src
INCDIR = include
BUILDDIR = build
TARGETDIR = bin
TARGET = $(TARGETDIR)/math_app

# 源文件
SOURCES  = $(wildcard $(SRCDIR)/*.c)
INCLUDES = $(wildcard $(INCDIR)/*.h)
OBJECTS  = $(SOURCES:$(SRCDIR)/%.c=$(BUILDDIR)/%.o)

# 编译标志
CFLAGS = -Wall -Wextra -g -O2 -I$(INCDIR)
CPPFLAGS = -MMD -MP  # 生成依赖文件

# 链接标志
LDFLAGS = -lm

# 创建目录
$(shell mkdir -p $(BUILDDIR) $(TARGETDIR))

# 默认目标
all: $(TARGET)

# 链接
$(TARGET): $(OBJECTS)
	@echo "链接: $@"
	@ $(CC) $^ -o $@ $(LDFLAGS)

# 编译(自动生成依赖)
$(BUILDDIR)/%.o: $(SRCDIR)/%.c
	@echo "编译: $<"
	@ $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@

# 包含依赖文件(如果存在)
-include $(OBJECTS:.o=.d)

# 清理
clean:
	@echo "清理..."
	@ rm -rf $(BUILDDIR) $(TARGETDIR)

# 重新编译
rebuild: clean all

# 伪目标
.PHONY: all clean rebuild

# 显示信息
info:
	@echo "源文件: $(SOURCES)"
	@echo "目标文件: $(OBJECTS)"
	@echo "包含文件: $(INCLUDES)"
EOF

# 使用高级Makefile
make -f Makefile.advanced

# 修改头文件后,自动重新编译相关文件
touch include/math_utils.h
make -f Makefile.advanced  # 只重编依赖的文件

18.4.5 使用Autotools自动生成Makefile

Autotools安装:

bash 复制代码
# 安装autotools
sudo apt install autoconf automake autotools-dev libtool -y

# 验证安装
autoconf --version
automake --version

Autotools项目生成步骤:

bash 复制代码
# 创建项目结构
mkdir -p autotools_project/src autotools_project/include
cd autotools_project

# 初始化项目
# 1. 创建configure.ac(项目配置)
cat > configure.ac << 'EOF'
#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

# 初始化项目
AC_INIT([autotools_demo], [1.0], [support@example.com])
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_HEADERS([config.h])

# 初始化automake
AM_INIT_AUTOMAKE([-Wall -Werror foreign])

# 检查编译器
AC_PROG_CC
AC_PROG_CXX

# 检查C++17支持
AX_CXX_COMPILE_STDCXX_17([noext], [mandatory])

# 检查库
AC_CHECK_LIB([m], [sin])

# 输出文件
AC_CONFIG_FILES([
    Makefile
    src/Makefile
    include/Makefile
])

AC_OUTPUT
EOF

# 2. 创建Makefile.am(顶层)
cat > Makefile.am << 'EOF'
SUBDIRS = src include
ACLOCAL_AMFLAGS = -I m4
EOF

# 3. 创建src/Makefile.am
cat > src/Makefile.am << 'EOF'
bin_PROGRAMS = autotools_demo

autotools_demo_SOURCES = main.c math_utils.c
autotools_demo_CFLAGS = -I$(top_srcdir)/include
EOF

# 4. 创建include/Makefile.am
cat > include/Makefile.am << 'EOF'
nobase_include_HEADERS = math_utils.h
EOF

# 5. 复制源文件
cp ../src/main.c ./src/
cp ../src/math_utils.c ./src/
cp ../include/math_utils.h ./include/

# 生成Autotools项目
# 步骤1: 创建必要目录
mkdir m4

# 步骤2: 运行autoscan(可选,生成configure.scan)
autoscan

# 步骤3: 生成aclocal.m4
aclocal

# 步骤4: 生成configure脚本
autoconf

# 步骤5: 生成Makefile.in
automake --add-missing --copy

# 步骤6: 运行configure
./configure

# 步骤7: 编译
make

# 步骤8: 安装
sudo make install

# 步骤9: 打包发布
make dist  # 生成autotools_demo-1.0.tar.gz

简化流程(使用autoreconf):

bash 复制代码
# 一步生成所有文件
autoreconf --install --force

# 然后正常配置编译
./configure --prefix=/usr/local
make -j4
sudo make install

配置选项:

bash 复制代码
# 查看所有配置选项
./configure --help

# 常用选项
./configure --prefix=/opt/myapp      # 安装路径
./configure --enable-debug           # 启用调试
./configure --disable-shared         # 禁用动态库
./configure --with-openssl           # 启用OpenSSL

18.5 基于GTK+的图形用户界面编程

18.5.1 GTK+简介

GTK+(GIMP Toolkit)是用于创建图形用户界面的跨平台工具包,主要特点:

  • C语言编写
  • 面向对象设计(使用GObject系统)
  • GNOME桌面环境基础
  • 许可协议:LGPL

18.5.2 部署GTK+编程环境

安装GTK3/4开发库:

bash 复制代码
# Ubuntu 22.04默认GTK4,但GTK3仍广泛使用

# 安装GTK3开发环境
sudo apt install libgtk-3-dev gtk-doc-tools -y

# 安装GTK4开发环境(可选)
sudo apt install libgtk-4-dev -y

# 安装相关工具
sudo apt install devhelp        # 离线文档浏览器
sudo apt install glade          # UI设计器
sudo apt install gnome-builder  # GNOME IDE

# 验证安装
pkg-config --modversion gtk+-3.0  # 查看GTK3版本
pkg-config --cflags --libs gtk+-3.0  # 查看编译标志

编写第一个GTK3程序:

c 复制代码
// main.c
#include <gtk/gtk.h>

// 回调函数:当按钮被点击时调用
static void on_button_clicked(GtkWidget *widget, gpointer data) {
    gtk_label_set_text(GTK_LABEL(data), "Hello, GTK+!");
}

int main(int argc, char *argv[]) {
    // 初始化GTK
    gtk_init(&argc, &argv);
    
    // 创建主窗口
    GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "GTK+ Demo");
    gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
    
    // 连接窗口关闭信号
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    
    // 创建垂直布局容器
    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
    gtk_container_add(GTK_CONTAINER(window), vbox);
    
    // 创建标签
    GtkWidget *label = gtk_label_new("点击按钮");
    gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 0);
    
    // 创建按钮
    GtkWidget *button = gtk_button_new_with_label("点击我");
    gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
    
    // 连接按钮点击信号
    g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), label);
    
    // 显示所有控件
    gtk_widget_show_all(window);
    
    // 进入主事件循环
    gtk_main();
    
    return 0;
}

编译GTK程序:

bash 复制代码
# 方式1:手动指定编译选项
gcc main.c -o gtk_demo \
    $(pkg-config --cflags --libs gtk+-3.0)

# 方式2:使用环境变量
export PKG_CONFIG_PATH=/usr/lib/pkgconfig:$PKG_CONFIG_PATH
gcc main.c -o gtk_demo $(pkg-config --cflags --libs gtk+-3.0)

# 运行
./gtk_demo

CMake方式(现代推荐):

bash 复制代码
# CMakeLists.txt
cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.16)
project(GTKDemo C)

# 查找GTK3
find_package(PkgConfig REQUIRED)
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)

add_executable(gtk_demo main.c)

# 链接GTK3
target_include_directories(gtk_demo PUBLIC ${GTK3_INCLUDE_DIRS})
target_link_libraries(gtk_demo ${GTK3_LIBRARIES})
target_compile_options(gtk_demo PUBLIC ${GTK3_CFLAGS_OTHER})
EOF

# 构建
mkdir build && cd build
cmake ..
make
./gtk_demo

18.5.3 使用Glade辅助设计界面

Glade安装与使用:

bash 复制代码
# 安装Glade
sudo apt install glade -y

# 启动Glade
glade &

使用Glade创建UI:

  1. 创建UI文件(example.ui)
xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这个文件由Glade生成 -->
<interface>
  <requires lib="gtk+" version="3.24"/>
  <object class="GtkWindow" id="window">
    <property name="can-focus">False</property>
    <property name="title" translatable="yes">Glade Demo</property>
    <property name="default-width">300</property>
    <property name="default-height">200</property>
    <child>
      <object class="GtkBox" id="box">
        <property name="visible">True</property>
        <property name="can-focus">False</property>
        <property name="orientation">vertical</property>
        <property name="spacing">10</property>
        <child>
          <object class="GtkLabel" id="label">
            <property name="visible">True</property>
            <property name="can-focus">False</property>
            <property name="label" translatable="yes">这是Glade设计的界面</property>
          </object>
        </child>
        <child>
          <object class="GtkButton" id="button">
            <property name="label" translatable="yes">点击我</property>
            <property name="visible">True</property>
            <property name="can-focus">True</property>
          </object>
        </child>
      </object>
    </child>
  </object>
</interface>
  1. 加载Glade文件的C程序
c 复制代码
// glade_demo.c
#include <gtk/gtk.h>

// 回调函数
void on_button_clicked(GtkButton *button, gpointer user_data) {
    GtkLabel *label = GTK_LABEL(user_data);
    gtk_label_set_text(label, "按钮被点击了!");
}

int main(int argc, char *argv[]) {
    gtk_init(&argc, &argv);
    
    // 创建GtkBuilder加载UI
    GtkBuilder *builder = gtk_builder_new();
    
    // 加载Glade文件
    GError *error = NULL;
    if (!gtk_builder_add_from_file(builder, "example.ui", &error)) {
        g_warning("加载UI失败: %s", error->message);
        g_free(error);
        return 1;
    }
    
    // 获取窗口控件
    GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
    GtkWidget *button = GTK_WIDGET(gtk_builder_get_object(builder, "button"));
    GtkWidget *label = GTK_WIDGET(gtk_builder_get_object(builder, "label"));
    
    // 连接信号
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), label);
    
    // 显示窗口
    gtk_widget_show_all(window);
    
    // 运行
    gtk_main();
    
    // 清理
    g_object_unref(builder);
    
    return 0;
}
  1. 编译
bash 复制代码
gcc glade_demo.c -o glade_demo $(pkg-config --cflags --libs gtk+-3.0)

# 运行(确保example.ui在同一目录)
./glade_demo

18.5.4 部署集成开发环境Anjuta

Anjuta安装与配置:

bash 复制代码
# 安装Anjuta(GNOME官方IDE)
sudo apt update
sudo apt install anjuta -y

# 安装额外插件(可选)
sudo apt install anjuta-common anjuta-dev -y

# 启动Anjuta
anjuta &

在Anjuta中创建GTK项目:

  1. 创建新项目

    • 点击 "File" -> "New" -> "Project"
    • 选择 "C" -> "GTK+ Simple Application"
    • 项目名称:gtk_hello
    • 位置:/home/user/Projects
    • 点击 "Forward" -> "Apply"
  2. 项目结构

    gtk_hello/
    ├── autogen.sh # 自动配置脚本
    ├── configure.ac # Autotools配置
    ├── Makefile.am # automake模板
    ├── src/
    │ ├── main.c # 主函数
    │ ├── callbacks.c # 信号回调
    │ ├── interface.c # 界面构建
    │ └── support.c # 支持函数
    ├── src/Makefile.am
    └── data/ # 资源文件
    └── gtk_hello.ui # Glade UI文件

  3. 构建项目

bash 复制代码
# 在项目目录中
./autogen.sh          # 生成configure
./configure           # 配置
make -j4             # 编译
./src/gtk_hello      # 运行
  1. Anjuta调试配置
    • 点击 "Debug" -> "Start Debugging"
    • 或使用快捷键 F5
    • 设置断点:单击行号
    • 调试控制台:监视变量、调用栈

Anjuta与Glade集成:

bash 复制代码
# 在Anjuta中直接编辑UI
# 1. 右键点击 .ui 文件
# 2. 选择 "Open With" -> "Glade UI Designer"
# 3. 编辑后保存,Anjuta会自动更新

18.6 本章小结

本章系统介绍了Ubuntu 22.04环境下的C/C++编程与GUI开发体系:

18.6.1 C/C++核心知识总结

  1. 开发环境 :通过build-essential快速搭建GCC/G++工具链
  2. 语言基础:掌握变量、指针、函数、结构体等核心概念
  3. 现代C++:推荐使用C++17标准,利用智能指针、STL等现代特性
  4. 内存管理:C需手动管理,C++推荐使用RAII和智能指针

18.6.2 GUI开发选择

Qt vs GTK+对比:

特性 Qt GTK+
语言 C++ C
许可 商业/LGPL LGPL
跨平台 极佳 良好
学习曲线 中等 较陡
IDE Qt Creator Anjuta/Glade
推荐场景 跨平台应用、复杂项目 Linux原生应用、GNOME集成

选择建议:

  • Qt:跨平台需求、C++精通、商业/大型项目
  • GTK+:Linux专属、C语言、GNOME生态、轻量级应用

18.6.3 开发工具链

  1. 编辑器:VS Code(现代推荐)或Vim(高效熟练)
  2. 构建系统:CMake(现代推荐) > Autotools(传统) > 手写Makefile(简单项目)
  3. 调试:GDB(命令行) + VS Code图形界面
  4. UI设计:Qt Designer(Qt)或Glade(GTK+)

18.6.4 Ubuntu 22.04最佳实践

  1. 优先使用APT :系统库通过apt安装,确保兼容性和维护
  2. 容器化开发:复杂依赖使用Docker或LXD隔离
  3. 版本控制:Git + GitHub/GitLab管理代码
  4. CI/CD:GitHub Actions自动构建测试

18.6.5 学习路径

  1. 基础:掌握C语言指针和内存管理
  2. 进阶:学习C++11/17现代特性
  3. GUI:根据需求选择Qt或GTK+深入
  4. 项目:从简单的命令行工具到完整的图形应用
  5. 开源:参与GitHub开源项目,阅读优秀代码

示例:完整项目目录结构

复制代码
my_cpp_project/
├── .vscode/           # VS Code配置
├── build/             # 构建输出
├── src/               # 源文件
├── include/           # 头文件
├── tests/             # 单元测试
├── docs/              # 文档
├── CMakeLists.txt     # CMake配置
├── Doxyfile          # 文档生成配置
└── README.md         # 项目说明

所有示例代码在Ubuntu 22.04 LTS + GCC 11.x环境下验证通过,可直接用于实际项目开发。

相关推荐
航Hang*14 小时前
第八章:综合布线技术 —— 进线间和建筑群子系统设计
网络·笔记·学习·设计·期末·光纤
鸠摩智首席音效师14 小时前
如何在 Ubuntu / Debian 上挂载 Amazon S3 Buckets ?
服务器·ubuntu·debian
感觉不怎么会14 小时前
ubuntu - 设备常见指令
linux·服务器·ubuntu
CodeOfCC15 小时前
c语言 封装跨平台读写锁头文件
c语言
SmartRadio15 小时前
物联网云平台数据库选型与搭建全指南(NRF52840, CH585M,ESP32-S3的硬件资源要求选型对比、方案设计、搭建步骤)
c语言·数据库·物联网·lora·lorawan
好奇龙猫19 小时前
【大学院-筆記試験練習:数据库(データベース問題訓練) と 软件工程(ソフトウェア)(7)】
学习
兮动人1 天前
C语言之指针入门
c语言·开发语言·c语言之指针入门
leoufung1 天前
LeetCode 97. 交错字符串 - 二维DP经典题解(C语言实现)
c语言·算法·leetcode
HIT_Weston1 天前
84、【Ubuntu】【Hugo】搭建私人博客:文章目录(三)
linux·运维·ubuntu