程序设计实验大作业编写过程中学习到的杂项记录,仅供参考~
OOP
由于C模拟OOP较为复杂, 此处仅简要记录, 实际开发未能使用.
object-oriented programming, 面向对象编程.
对象: 某一类事物的抽象概念.
类: 对象的表现; 具有属性(变量)和方法(函数).
实例: 根据类构建出的具体事物.
封装: 不需要被外界访问的属性和方法私有化(private); 接口与实现分离, 只暴露接口, 无需关心实现; 良好封装可以解耦合.
继承: 从现有类构造出新的类, 属性和方法(protected)被继承; 破坏了封装, 父类实现对子类暴露; 强耦合, 父类改变时子类随之而变.
多态: 具体类型和方法在编程时不确定, 运行时(编译时)可选择多个状态并最终确定; 编译时多态即函数重载/符号重载.
Git
记录快照和hash值.
modified --(add)–> staged --(commit)–> committed
# 配置
git config
--list # 查看配置
--global user.name ["用户名"]
--global user.email [邮箱]
<key> # 查看某项配置
# 基本
git init
git diff # 未暂存的改动
diff --staged # 或"--cached", 已暂存的改动
git add [文件名] # 暂存, "."通配
git status # 暂存文件
git reset [文件名] # 撤销暂存
git rm --cached [文件名] # 从暂存区移除
git commit -m "提交信息"
--amend # 补充提交, 新提交覆盖旧提交
git rm -f [文件名] # 不再追踪
git log --patch # 提交记录, 补丁形式
--stat # 文件总结
--grep [字符串] # 提交说明中包含指定内容
-S [字符串] # 添加或删除内容中包含指定内容
-[n] # 最近的n条提交
--after [时间] # 指定时间后的提交
--before [时间] # 指定时间前的提交
--pretty=format:"%h - %cd, %s" --graph # 图形显示提交hash简写, 日期, 说明
git reset # 默认"--mixed", 回滚, 差异放在工作区
--soft # 回滚, 差异放在暂存区
--hard # 回滚, 不保存差异
# .gitignore 忽略文件, glob模式匹配, !表示取反
# 远程仓库
git remote add [简称] [链接]
-v # 远程简称和对应链接
rename [旧简称] [新简称]
show [简称]
remove [简称] # 移除
git clone [链接] [本地路径]
git fetch [简称] # 拉取(不合并)
git pull [简称] # 合并
git push [简称] [分支] # 提交
# 分支
git branch [分支] # 新建
git branch -a # 查看全部
--merged # 查看已合并
--no-merged # 查看未合并
-d [分支] # 删除
git log --decorate # 各个分支所指对象
git checkout [分支] # 切换
-b [分支] # 新建并切换
git merge [合入分支] # 快速合并
--no-ff [合入分支] # 非快速合并, 合并到当前分支的新节点
--squash [合入分支] # 压缩合并, 合并到当前分支的新节点, 但不保留对合入分支的引用
--rebase [合入分支] # 变基合并, 合入分支变基到当前节点
git cherry-pick -[n] commit # 拣选n个节点合并
提交信息格式(推荐): “<type>(<scope>): <subject>”
<type>: feat(feature) 新功能; fix/to 修复bug; docs 文档; style 修改格式; refactor 重构; perf 优化; test 增加测试; chore 构建过程或辅助工具变动; revert 回滚; merge 合并分支.
<scope>: 影响范围.
项目结构
函数接口 | 函数实现 |
---|---|
头文件(.h) | 源文件(.c)/静态链接库(.a/.lib)/动态链接库(.so/.ddl) |
库文件: 目标文件(.o)的压缩.
静态链接库: 生成的可执行文件可独立运行; 重复调用的模块在链接时被多次复制, 造成代码冗余.
动态链接库(共享链接库): 链接时记录模块位置; 生成的可执行文件无法独立运行.
目录结构
xxx-build/: 构建和编译目录; 下有子目录debug/和release/;在clion中为自动生成; 一般无需追踪
.git/和.gitignore: 版本控制
dist/: 分发目录, 最终发布版本; 下可按版本号划分子目录
docs/: 文档目录
include/: 公共头文件目录; 下可按模块划分子目录
lib/: 外部依赖库目录
src/: 源文件目录; 与头文件目录同结构同名
samples/: 样例程序目录
tests/: 测试文件目录
tools/: 支撑工具目录
copyright: 版权声明文件
CMakeLists.txt或Makefile: 构建配置文件
README: 说明文件
GCC -> Makefile -> CMake
编译过程: .c --(预处理)–> .i --(编译egcs)–> .s --(汇编as)–> .o --(链接ld)–> 可执行文件.
ar rcs [libxxx.a] [.o] # 生成静态链接库, 名称必须以"lib"开头, win下为"libxxx.lib"
gcc -fPIC -shared [.c] -o [libxxx.so] # 生成动态链接库, 名称必须以"lib"开头, win下为"libxxx.dll"
# 基本
gcc -E [.c] -o [.i] # 仅预处理, 需重定向输出
-S [.c/.i] -o [.s] # 预处理和编译, 生成.s
-c [.c/.i/.s] -o [.o] # 预处理, 编译, 汇编, 生成.o
[.c/.i/.s/.o] -o [out] # 预处理, 编译, 汇编, 生成可执行文件
-I [path] [.o] # 指明头文件路径
-staic [.o] [libxxx.a] # 链接静态库
-L [path] -l[xxx] # 指明静态库路径和静态库
[.c] [.so] -o [out] # 链接动态库
ldd [out] # 查看运行时所需的动态链接库及位置
# 动态链接库生成的可执行文件无法单独运行
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx # 仅当前Terminal生效
# 运行时链接动态库
# linux: <dlfcn.h> dlopen, dlsym, dlclose, dlerror
# windows: <windows.h> LoadLibraryA, GetProcAddress, FreeLibrary
# 调试和功能
-v # 输出详细编译过程
-Q # 输出编译相关统计信息
-Wall # 输出警告
-Werror # 警告视为错误
-g # "-ggdb"或"-g2", 生成可调式的执行文件, debug模式
-O # "-O1", 基本优化
-O2 # 进一步优化, 会增加文件大小, release模式
-O3 # 较激进优化, 会增加文件大小
# makefile (仅linux)
make
make clean
# 版本1
[out]: [.c]
gcc -o [out] [.c] # tab缩进, 不能使用空格, 下同
# 版本2
C = gcc
TARGET = [out]
OBJ = [.o]
$(TARGET): $(OBJ)
$(C) -o $(TARGET) $(OBJ)
[.o]: [.c]
$(C) -c [.c]
# 版本3
C = gcc
TARGET = [out]
OBJ = [.o]
CFLAGS = -c -Wall
$(TARGET): $(OBJ)
$(C) -o $@ $^
%.o: %.c
$(C) $(CFLAGS) $< -o $@
.PHONY: clean # 避免文件名为clean造成二重含义
clean:
rm -f *.o $(TARGET)
# 版本4
C = gcc
TARGET = [out]
SRC = $(wildcar *.c)
OBJ = $(patsubst %.cpp, %.o, $(SRC))
CFLAGS = -c -Wall
$(TARGET): $(OBJ)
$(C) -o $@ $^
%.o: %.c
$(C) $(CFLAGS) $< -o $@
.PHONY: clean
clean:
rm -f *.o $(TARGET)
cmake -S . -B cmake-build -D[CACHE]=[value]
# 现代CMake: 基于target和module
cmake_minimum_required()
project()
add_subdirectory()
add_library() # STATIC/SHARED
add_executable()
target_include_directions() # PRIVATE/PUBLIC/INTERFACE
target_include_directories()
target_compile_features()
target_compile_options()
include(CTest)
enable_testing()
# 变量 set(); option(); list()
# 条件 if()/elseif()/else()/endif()
# 循环 foreach()/endforeach(); while()/endwhile(); continue(); break()
# 宏 marco()/endmacro()
# 函数 function()/endfunction()
# 输出 message()
# 文件操作 file()
# 支持glob模式匹配, bash调用方式 ${file_name}; GLOB_RECURSE递归匹配
# pch: 预编译头文件
target_precompile_headers() # REUSE_FROM
# 自定义命令
add_custom_command() # COMMAND
自动生成文档 (Doxygen)
/** 文件注释
* @file
* @brief
* @details
* @author
* @version
* @date
* @copyright
*/
/// 变量或常量前注释
///< 变量或常量后注释
/** 函数注释
* @brief
* @details
* @param[in]
* @param[out]
* @return
* @retval
* @attention
* @warning
* @exception
*/
/** 可选项
* @note
* @remarks
* @example
* @see
* @var
* @enum
* @code @endcode
* @bug
* @todo
* @pre
* @post
* @deprecated
*/
// 特殊标识
// TODO 代办
// FIXME 需修正
单元测试
对每个单元(函数/方法/类/子模块)的输出和异常进行测试.
以下为goolgetest方法, 由于兼容问题实际并未使用.
// MACRO
TEST (globalConfigurationTest, configurationDataTest)
// Assert: [(non)fatal]_[type]
// (non)Fatal: ASSERT_, fatal; EXPECT_, nonfatal
// Basic: _TRUE(condition); _FALSE(condition)
// Binary: _EQ(var1, var2), equal; _NE(var1, var2), not equal
// _LT(var1, var2), less than; _LE(var1, var2), less equal
// _GT(var1, var2), big than; _GE(var1, var2), big equal
// String: _STREQ(str1, str2), same content; _STRNE(str1, str2), different content
// _STRCASEEQ(str1, str2), same ignoring case; _STRCASENE(str1, str2), different ignoring content
// Float: _FLOAT_EQ(var1, var2), almost equal 4ULP; _DOUBLE_EQ(var1, var2), almost equal 4ULP
// _NEAR()_EQ(var1, var2, abs_error), almost equal
// Exception: _THROW(statement, exception_type); _ANY_THROW(statement); _NO_THROW(statement)
// SUCESS(); FAIL(), fatal; ADD_FAIL, nonfatal
远程桌面 (RDP over SSH)
需求为win10客户端远程连接win10服务端.
Get-WindowsCapability -Online | Where-Object Name -like 'OpenSSH*' # 查询
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0 # 安装客户端
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0 # 安装服务端(客户端无需安装)
Set-Service -Name sshd -StartupType 'Automatic' # 开机启动
Start-Service sshd # 启动服务
修改"C:\ProgramData\ssh\sshd_config"文件, 若无权限保存则复制替换.
修改 Port 选项为55555.
修改 PermitRootLogin 选项为 no.
修改 PasswordAuthentication 选项为 no.
不注释 “PubkeyAuthentication yes”.
不注释 “AuthorizedKeysFile .ssh/authorized_keys”.
注释 “Match Group administrators”.
注释 “AuthorizedKeysFile PROGRAMDATA/ssh/administrators_authorized_keys”.
ssh-keygen # 生成rsa密钥对
# file 可选项, 默认为"C:\Users\username/.ssh/id_rsa."
# passphrase 可选项, 载入私钥时需要的口令, 主要为保障私钥安全
# 私钥拷贝至客户端
# 客户端建立ssh隧道
ssh <username>@<Internet ip> -p 33333 -L 12345:127.0.0.1:55555 -N -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -f
# 公网IP可以通过内网穿透实现
项目细节
命令行参数解释
# 一般命令行参数解释
main -s --long -n 100 /src
# 短参数 长参数 带值参数 捕获组
# "-n 100", "--std=c11", "-lhello"
# option flag: 空格 等号 粘连
# 类型: bool, 数值, 字符串, 捕获组
# 扩展功能(用户层): 错误提示, 自动help, 子命令
内存问题
// 函数外部已经分配好的堆上/栈上字符串
int OpStr(char *dest, char *src, size_t len);
// 函数内部需要分配堆上字符串
int OpStr2(char **dest, char *src, size_t len)
{
char *dest = (char*) calloc(len + 1, 1);
return 0;
}
文件读写
文件读入, 路径文件不存在时读入为字符串.
char* GetStringFromFile(size_t size)
{
char* begin = NULL, *end = NULL;
for (end = begin = (char*)calloc(size, 1); (*end = (char)getchar()) != '\n'; ++end);
if (end == begin) exit(EXIT_FAILURE);
*end = '\0';
FILE *fp = fopen(begin, "rb");
if (fp)
{
fseek(fp, 0, SEEK_END);
size_t fileSize = ftell(fp);
char *str = (char*) calloc(fileSize, 1);
if (!str) return NULL;
rewind(fp);
if (fread(str.Begin, 1, fileSize, fp) != fileSize) return NULL;
fclose(fp);
free(begin);
}
else char *str = begin;
return str;
}
写入文件.
int PutStringToFile(char *str)
{
char* begin = NULL, *end = NULL;
for (end = begin = (char*)calloc(size, 1); (*end = (char)getchar()) != '\n'; ++end);
if (end == begin) exit(EXIT_FAILURE);
*end = '\0';
FILE *fp = fopen(begin, "wb");
size_t size = strlen(str);
if (fwrite(str, 1, size, fp) != size) return -1;
return 0;
}
Base64读写
以ascii读入, 需要进行转换, 每4个base64字符为一组存入3个Byte.
typedef unsigned char Byte;
const static Byte ascii[80] =
{
62, -1, -1, -1, 63, 52, 53, 54, 55, 56,
57, 58, 59, 60, 61, -1, -1, -1, 65, -1,
-1, -1, 0, 1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, -1, -1,
-1, -1, -1, -1, 26, 27,28, 29, 30, 31,
32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
42, 43, 44, 45, 46, 47, 48, 49, 50, 51
};
void example(void)
{
char str[5] = "cat+";
Byte trs[4];
for (int i = 0; i < 4; ++i) trs[i] = ascii[str[i] - '+'];
// ascii从"+"开始
Byte b[3] = {
0};
b[0] = ((trs[0] << 2) & 0B11111100) + ((trs[1] >> 4) & 0B11);
b[1] = ((trs[1] << 4) & 0B11110000) + ((trs[2] >> 2) & 0B1111);
b[2] = ((trs[2] << 6) & 0B11000000) + ((trs[3] >> 0) & 0B111111);
printf("%x %x %x\n", b[0], b[1], b[2]);
}
末尾有2个"=“时, 最后一组只有1个Byte; 末尾有1个”="时, 最后一组只有2个Byte.
补尾规则: 1个"=“时, 最后字符补"00"得到, 即”=“前字符须整除4; 2个”=“时, 最后字符补"0000"得到, 即”="前字符须整除16.
bit | Byte | base64(“=”) |
---|---|---|
64 | 8 | 12(1) |
128 | 16 | 24(2) |
256 | 32 | 44(1) |
512 | 64 | 88(2) |
1024 | 128 | 172(1) |
base64(“=”) | Byte |
---|---|
4n(0) | 3n |
4n(1) | 3n-1 |
4n(2) | 3n-2 |
每3个Byte为一组输出4个base64字符.
typedef unsigned char Byte;
const static char base[64] =
{
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
'w', 'x', 'y', 'z', '0', '1', '2', '3',
'4', '5', '6', '7', '8', '9', '+', '/'
};
void example(void)
{
Byte b[3] = {
0x71, 0xab, 0x7e};
Byte trs[4] = {
0};
trs[0] = (b[0] >> 2) & 0B111111;
trs[1] = ((b[0] << 4) & 0B110000) + ((b[1] >> 4) & 0B1111);
trs[2] = ((b[1] << 2) & 0B111100) + ((b[2] >> 6) & 0B11);
trs[3] = b[2] & 0B111111;
char str[5] = "";
for (int i = 0; i < 4; ++i) str[i] = base[trs[i]];
printf("%s\n", str);
}
Byte | base64(“=”) |
---|---|
3n | 4n(0) |
3n+1 | 4n+4(2) |
3n+2 | 4n+4(1) |
剩余1Byte时, 增加1个空Byte, 末尾补2个"=“, 剩余2Byte时, 增加1个空Byte, 末尾补1个”=".
单元转换
当最小操作单元与最小读写单元一致时, 小端序或大端序实现均不会影响结果.
当最小操作单元与最小读写单元不一致时, 需要进行单元转换; 而直接修改指针类型会由于小端序而错误读取.
typedef unsigned char Byte;
typedef unsigned int Word;
void example(void)
{
// Word to Byte
Word a = 0x12345678;
Byte d[4];
d[0] = (a >> 8 * (4 - 1)) & 0xff;
d[1] = (a >> 8 * (3 - 1)) & 0xff;
d[2] = (a >> 8 * (2 - 1)) & 0xff;
d[3] = a & 0xff;
printf("%x %x %x %x\n", d[0], d[1], d[2], d[3]);
// Byte to Word
Word g = 0;
g += ((Word) d[0] << 8 * (4 - 1));
g += ((Word) d[1] << 8 * (3 - 1));
g += ((Word) d[2] << 8 * (2 - 1));
g += (Word) d[3];
printf("%x\n", g);
}
字节流
为便于 Bit Padding 和加解密处理, 和出于安全考虑, 对比特流加密前先将字符串型转换为字节流, 结构如下:
typedef unsigned char Byte;
typedef struct
{
Byte* begin;
Byte* end;
} ByteStream;
对 begin 使用内存分配函数, end 始终指向字节流末尾.
Bit Padding
可分为直接填充和保留长度信息填充, 直接填充即填充到分组的整数倍, 保留长度信息填充也填充到整数倍, 但最后预留一定位置存储长度信息.
而保留长度填充可在直接填充基础上, 比较填充余量和长度预留, 不足时额外增加一个分组即可.
size_t BitPaddingSize(int strSize, int blockSize, int remains)
{
int flag = strSize % blockSize ? 1 : 0;
size_t padSize = (strSize / blockSize + flag) * blockSize - strSize;
if (remains && (padSize % blockSize) <= remains) padSize += blockSize;
size_t newSize = strSize + padSize;
return newSize;
}