简介:C4Droid 手机 C++ 编程资源包面向希望在 Android 手机或平板上随时编写、编译和运行 C/C++ 代码的学习者与开发者,适合课堂练习、算法验证、小型项目原型及无电脑环境下的移动编程。包内共 13 个文件,以 7 个 apk 安装程序为主,辅以 cpp、h 源码示例、txt 说明、png 截图及 zip 压缩包,整体约 48.91MB,便于在移动端快速部署编程环境。已有 724 人学习下载,说明该工具在移动开发入门群体中具有一定参考价值。资源围绕 C4Droid 的终端式编辑、编译与运行流程展开,兼顾 TCC 轻量编译和 GCC 插件扩展思路,可配合 STL、SDL、Qt 等库了解图形界面、多媒体与网络通信程序的开发方式;附带的终端、键盘与截屏类工具也能改善手机端编程体验,适合作为移动 C++ 学习与调试的入门工具包。
1. 从一部旧安卓机说起:c4droid 把手机变成 C++ 编译终端
手机编程工具最尴尬的地方不是功能少,而是“看起来能写,真跑起来处处断链”。某开发者把一台退役安卓机清空后装上 c4droid,只保留编译器和键盘,地铁上改两行 C++、编译、看终端输出,全程不碰电脑。c4droid 把 GCC/Clang 工具链、代码编辑器和终端塞进同一块屏幕,能跑多文件工程,也能把 SDL2 窗口推到手机上。它适合想用碎片时间验证算法、出门在外改小工程、或者给学生演示 C++ 编译链路的人;不适合指望它替代桌面 IDE 的人。这一篇按“装好怎么验、工程怎么拆、图形库怎么链、翻车怎么查”的顺序拆一遍。
2. 环境落地:三条命令验工具链,再跑通第一个多文件工程
装好主程序和编译器插件之后,别急着写业务代码。手机端最怕“编辑器能高亮,编译器没装上”。C4droid 的编译按钮最终调用的还是 g++ 或 clang++,你看到的报错、链接顺序、头文件搜索路径,跟桌面命令行同源。把终端当成黑匣子,后面所有问题都只能猜。先让终端说话,把编译器身份、头文件搜索路径、标准库可用性这三件事确认掉,再谈工程结构。
2.1 三条命令确认编译器不是摆设
先在应用内终端进入工程目录,执行下面三条命令。第一条看编译器是否存在,第二条看预处理器的头文件搜索路径,第三条看当前编译器默认的 C++ 标准宏。手机终端可能没有某些过滤工具,没有就整段输出往下翻,不要因为一条 sed 报错就以为编译器坏了。
# 1. 看编译器身份与目标平台 g++ --version clang++ --version # 2. 看预处理器的头文件搜索路径 printf '' | g++ -E -v -x c++ - 2>&1 # 3. 看默认标准宏和平台宏 printf '' | g++ -dM -E -x c++ - | grep -E '__cplusplus|__ANDROID__'第一条命令里的--version只做一件事:确认 g++ 或 clang++ 能被当前 shell 找到。如果返回command not found,说明编译器插件没装好,或者 PATH 没指到插件目录,后面写再多代码也没用。第二条命令里-E表示只做预处理,-v打印详细过程,-x c++强制按 C++ 处理,末尾的-表示从标准输入读空内容。重点看输出里#include <...> search starts here:到End of search list.之间的路径,这些就是尖括号头文件的搜索范围。
第三条命令里的-dM -E会把预定义宏全部打印出来,grep只留下__cplusplus和__ANDROID__。__cplusplus能看出默认标准是 C++11、C++14 还是 C++17,__ANDROID__能确认当前确实是安卓目标。常见误用是只看编辑器语法高亮就判断环境正常,结果一编译就报std::filesystem不存在,原因就是默认标准太低。后面所有编译命令都显式写-std=c++17,不要依赖默认值。
提示:如果
clang++不存在,不用纠结,以g++的输出为准;两个编译器混用时,头文件和库路径不要交叉引用。
2.2 最小工程:先只依赖标准库
验证工具链之后,写一个只依赖标准库的最小样例。这个样例不要碰 SDL、不要碰第三方库,只验证iostream、string、vector这三个最常见头文件能不能编译、链接、运行。手机键盘输入容易漏分号,代码越短越好。
// src/main.cpp #include <iostream> #include <string> #include <vector> int main() { std::vector<std::string> names{"A", "B", "C"}; for (const auto& name : names) { std::cout << "hello, " << name << '\n'; } return 0; }保存后执行下面两条命令。第一条创建输出目录,第二条编译并运行。mkdir -p的-p表示目录已存在也不报错,避免第二次执行时中断。
mkdir -p build g++ -std=c++17 -Wall -Wextra -O2 src/main.cpp -o build/hello ./build/hello参数逐个说清楚:-std=c++17固定语言标准,避免不同编译器默认值不一样;-Wall打开常见警告,-Wextra打开额外警告,手机端屏幕小,警告比桌面更容易被忽略,建议一开始就开;-O2做常规优化,调试阶段可以换成-O0 -g,生成调试信息后报错位置更准,但编译会慢一些;-o build/hello指定输出文件,不写的话默认输出到当前目录,产物会和源码混在一起。最后的./build/hello是运行产物,如果提示Permission denied,先确认文件确实生成,再执行chmod +x build/hello。
失败时只看第一条error,不要被后面几十行吓到。fatal error: iostream: No such file or directory通常说明编译器插件不完整,不是代码问题;expected ';'看对应行号;undefined reference to std::cout说明链接阶段没找到标准库,先回到 2.1 确认编译器身份。这个最小样例跑通,才说明标准库链路正常。接下来再加文件、加头文件、加第三方库,每加一层都单独验证一次,出问题容易定位。
注意:调试阶段把
-O2换成-O0 -g,不要一边优化一边猜变量值,手机端调试信息本来就不如桌面完整。
2.3 用 build.sh 固定参数,别每天手敲
手机键盘输入成本高,每次手敲一长串编译命令很容易漏参数。我一般会在工程根目录放一个build.sh,把编译器、标准、警告、输出目录固定下来。脚本不复杂,关键是可重复执行,换一台手机或者换一个终端目录,结果一致。
#!/bin/sh set -eu CXX=${CXX:-g++} CXXFLAGS=${CXXFLAGS:--std=c++17 -Wall -Wextra -O2} ROOT=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) cd "$ROOT" mkdir -p build $CXX $CXXFLAGS -Isrc src/main.cpp -o build/hello ./build/helloset -eu让脚本遇到错误立即退出,未定义变量也报错,避免“编译失败了但脚本继续往下跑”的假成功。CXX=${CXX:-g++}表示外部没传CXX就用 g++,想切 clang++ 时执行CXX=clang++ ./build.sh即可,不用改脚本。CXXFLAGS同理,调试时可以执行CXXFLAGS="-std=c++17 -Wall -Wextra -O0 -g" ./build.sh。ROOT那行先取脚本所在目录,再cd过去,这样在别的目录执行脚本也不会找不到src/main.cpp。
保存后执行chmod +x build.sh,再执行./build.sh。如果终端不支持CDPATH= cd --这种写法,简化成cd "$(dirname "$0")"也能用,别在兼容性上耗太久。常见失败是Permission denied,原因是脚本没有执行权限;not found看 shebang 第一行是不是/bin/sh;No such file or directory先pwd和ls确认当前目录。脚本跑通之后,后面加文件只需要改脚本里的源文件列表,编译参数不用每天重新敲。
3. 工程结构:把单文件 demo 拆成 include/src/build 三层
单文件能跑通只说明链路通,真写东西很快会遇到重复定义、头文件找不到、链接顺序错。手机屏幕小,更要把目录拆清楚。我一般用最小四层:include放声明,src放实现,third_party放外部头文件和库,build只放产物。不要把十几个.cpp堆在根目录,后面加文件时连自己都找不到,更别说排查链接错误。
3.1 目录布局与 include 路径
先建目录,再放代码。目录名用英文小写,短横线和下划线都比空格安全。安卓上路径大小写敏感,桌面能跑不代表手机能跑,统一小写能省很多事。
mkdir -p include src third_party/include third_party/lib build assetsinclude放自己写的头文件,src放实现和入口,third_party/include放外部库头文件,third_party/lib放外部库文件,build只放.o和可执行文件,assets放图片、字体、配置文件。这个布局不是强制标准,但它能把“自己的代码”和“外部依赖”分开,后面换库、删库、改链接路径时不容易误伤。
接着写一个最小的多文件样例。头文件里只放声明,实现文件里放定义,入口文件只负责调用。#pragma once用来防止同一个头文件被重复包含,比传统宏守卫少写几行。
// include/math_utils.h #pragma once int add(int a, int b); int mul(int a, int b);// src/math_utils.cpp #include "math_utils.h" int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; }// src/main.cpp #include <iostream> #include "math_utils.h" int main() { std::cout << add(2, 3) << '\n'; std::cout << mul(4, 5) << '\n'; return 0; }编译命令要显式加-Iinclude,让#include "math_utils.h"能找到头文件。引号包含的搜索顺序是先当前文件目录,再-I指定的目录;尖括号包含直接走系统路径和-I路径。自己的头文件用引号,标准库和第三方库用尖括号,这个习惯能减少路径混乱。
g++ -std=c++17 -Wall -Wextra -O2 -Iinclude \ src/main.cpp src/math_utils.cpp \ -o build/app如果报fatal error: math_utils.h: No such file or directory,先pwd看当前目录,再ls include看文件在不在,最后检查-Iinclude有没有写错。如果报undefined reference to add(int, int),说明头文件找到了但实现文件没参与编译,检查命令里有没有src/math_utils.cpp。这两个错误看起来都像“找不到”,但一个发生在预处理阶段,一个发生在链接阶段,处理方式完全不同。
3.2 Makefile:把源文件列表和链接顺序固定下来
源文件多了之后,手写编译命令会越来越长。Makefile 的价值不是“高级”,而是把源文件列表、头文件路径、链接库顺序固定下来,改一个文件只重编一个文件。手机端不一定自带 make,没有 make 就继续用build.sh,有 make 就用下面的最小模板。
CXX ?= g++ CXXFLAGS ?= -std=c++17 -Wall -Wextra -O2 -Iinclude LDFLAGS ?= LDLIBS ?= SRC := $(wildcard src/*.cpp) OBJ := $(patsubst src/%.cpp,build/%.o,$(SRC)) TARGET := build/app all: $(TARGET) $(TARGET): $(OBJ) $(CXX) $(LDFLAGS) $^ -o $@ $(LDLIBS) build/%.o: src/%.cpp @mkdir -p $(dir $@) $(CXX) $(CXXFLAGS) -MMD -MP -c $< -o $@ -include $(OBJ:.o=.d) clean: rm -rf build .PHONY: all cleanwildcard收集src下所有.cpp,patsubst把src/xxx.cpp换成build/xxx.o,这样新增源文件不用改 Makefile。$^表示所有依赖文件,$@表示目标文件,$<表示第一个依赖文件。-MMD -MP生成.d依赖文件,-include把它们包含进来,改头文件时相关源文件会自动重编。LDLIBS放在目标文件后面,因为链接器从左到右解析库,库放在源文件前面容易报未定义符号。
执行make或make -j2。手机核心数少,-j开太高反而发热降频,-j2是比较稳的起点。常见错误Makefile: *** missing separator基本是缩进用了空格,Makefile 的命令行必须以 tab 开头;No rule to make target 'build/xxx.o'看源文件路径和模式规则是否匹配。调试时执行make clean && make做一次干净构建,能排除旧产物干扰。
3
本文还有配套的精品资源,点击获取