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(图形用户界面)发展历程:
- X Window System (1984):Unix/Linux图形基础
- GTK (1997):GIMP工具包,C语言编写,GNOME桌面基础
- Qt (1995):C++框架,跨平台,KDE桌面基础
- 现代趋势: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配置步骤:
- 首次启动配置
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
- 创建第一个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. 点击"下一步"完成创建
-
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:
- 创建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>
- 加载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;
}
- 编译
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项目:
-
创建新项目
- 点击 "File" -> "New" -> "Project"
- 选择 "C" -> "GTK+ Simple Application"
- 项目名称:
gtk_hello - 位置:
/home/user/Projects - 点击 "Forward" -> "Apply"
-
项目结构
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文件 -
构建项目
bash
# 在项目目录中
./autogen.sh # 生成configure
./configure # 配置
make -j4 # 编译
./src/gtk_hello # 运行
- 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++核心知识总结
- 开发环境 :通过
build-essential快速搭建GCC/G++工具链 - 语言基础:掌握变量、指针、函数、结构体等核心概念
- 现代C++:推荐使用C++17标准,利用智能指针、STL等现代特性
- 内存管理: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 开发工具链
- 编辑器:VS Code(现代推荐)或Vim(高效熟练)
- 构建系统:CMake(现代推荐) > Autotools(传统) > 手写Makefile(简单项目)
- 调试:GDB(命令行) + VS Code图形界面
- UI设计:Qt Designer(Qt)或Glade(GTK+)
18.6.4 Ubuntu 22.04最佳实践
- 优先使用APT :系统库通过
apt安装,确保兼容性和维护 - 容器化开发:复杂依赖使用Docker或LXD隔离
- 版本控制:Git + GitHub/GitLab管理代码
- CI/CD:GitHub Actions自动构建测试
18.6.5 学习路径
- 基础:掌握C语言指针和内存管理
- 进阶:学习C++11/17现代特性
- GUI:根据需求选择Qt或GTK+深入
- 项目:从简单的命令行工具到完整的图形应用
- 开源:参与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环境下验证通过,可直接用于实际项目开发。