一、版本控制器Git:代码管理的瑞士军刀
不知道你工作或学习时,有没有遇到这样的情况:我们在编写各种文档时,为了防止文档丢失,更改失误,失误后能恢复到原来的版本,不得不复制出一个副本,比如;
"报告-v1"
"报告-v2"
"报告-v3"
"报告-确定版"
"报告-最终版"
"报告-究极进化版"
...
每个版本有各自的内容,但最终会只有一份报告需要被我们使用。
但在此之前的工作都需要这些不同版本的报告,于是每次都是复制粘贴副本,产出的文件就越来越多,文件多不是问题,问题是:随着版本数量的不断增多,你还记得这些版本各自都是修改了什么吗?
文档如此,我们写的项目代码,也是存在这个问题的!!
1.1 版本控制器的前世今生
- 为了能够更方便我们管理这些不同版本的文件,便有了版本控制器。所谓的版本控制器,就是能让你了解到一个文件的历史,以及它的发展过程的系统。通俗的讲就是一个可以记录工程的每一次改动和版本迭代的一个管理系统,同时也方便多人协同作业。
- 目前最主流的版本控制器就是Git。Git可以控制电脑上所有格式的文件,例如doc、excel、dwg、dgn、rvt等等。对于我们开发人员来说,Git最重要的就是可以帮助我们管理软件开发项目中的源代码文件!
- 📚 为什么需要版本控制?
为啥非得用版本控制?这玩意儿到底救过多少程序员的命?
说实话,没用过版本控制的人,大概都经历过这种绝望时刻:
场景一:代码回滚
-
不用版本控制:你手动备份了
项目_最终版.zip、项目_最终版_真的.zip、项目_最终版_不改了.zip,最后发现没一个能用的,文件夹里全是"最终版" -
用版本控制:淡定输入一条命令,秒回昨天那个能跑的版本,就像什么都没发生过
场景二:团队协作
-
不用版本控制:你改完代码发给同事,他改完发回来,你们互相覆盖,最后群里怒吼"谁改了我的文件!!"
-
用版本控制:各写各的,系统自动帮你们合并,冲突的地方标出来一起商量,优雅
场景三:找 Bug
-
不用版本控制:程序突然崩了,你盯着代码发呆,完全不知道哪行代码什么时候被谁动过
-
用版本控制:一行命令
git blame,直接定位到那个罪魁祸首的提交,连他写的提交信息都能看到
场景四:想做个实验
-
不用版本控制:复制整个项目文件夹,命名
项目_试试新功能,试完发现不行,删又不敢删,留着又占地方 -
用版本控制:
git checkout -b 实验分支,玩砸了直接删掉分支,干净无痕
场景五:硬盘挂了
-
不用版本控制:多年心血,一夜归零,坐在电脑前怀疑人生
-
用版本控制:换台电脑,
git clone,你的代码完完整整从云端回来
所以啊,版本控制就是程序员的时光机 + 协作神器 + 保险柜,这三件套缺一不可~
- 🔄 集中式 vs 分布式版本控制系统
集中式 vs 分布式:两种完全不同的世界观
这俩的区别,打个比方就像:
-
集中式(SVN):图书馆模式。书都在图书馆里,大家去那儿借书还书,家里不放藏书
-
分布式(Git):每个人都把整图书馆复印一份带回家,想怎么看就怎么看,改完了再互相交换更新
具体哪里不一样?
仓库长啥样
-
SVN:中央服务器是老大,里面存着所有历史记录,你的电脑上只有当前这一份代码,像个临时工
-
Git:你电脑上就有完整的仓库,从项目第一天到现在的每一次修改,全都存在你本地,像个档案馆
能不能离线干活
-
SVN:断网基本歇菜,想提交代码?没门,先连上网再说
-
Git:断网随便玩,提交、看历史、切分支统统本地搞定,等联网了再一次性同步上去
分支操作爽不爽
-
SVN:开分支就是复制整个目录,又慢又占地方,团队里没人爱用分支
-
Git:分支就是个指针,秒开秒切,一个人同时开十几个分支试不同想法都很轻松
数据安不安全
-
SVN:中央服务器要是挂了,大家手里都只有残缺的副本,慌得一批
-
Git:每个人的电脑都是完整备份,服务器炸了随便找个人 push 一下就能恢复
权限控制细不细
-
SVN:能精确控制到某个目录谁能看谁能改,适合管得严的公司
-
Git:主要在远程仓库层面管权限,本地你想怎么折腾都行
上手难不难
-
SVN:概念简单,checkout、commit、update 三板斧,新手一天学会
-
Git:概念多一丢丢,本地仓库、暂存区、远程仓库、分支合并,得花点时间适应,但学会了就回不去了
实际工作流程差多远?
用 SVN 的一天:
早上来公司,先连上内网,svn update 拉最新代码,开始写。
中午写完了,svn commit -m "修复登录bug",直接推到中央服务器。如果同事也改了同一处,冲突了?先 update,手动解决,再 commit。
下午断网了,想提交改了一半的代码?忍着吧,等网好了再说。
用 Git 的一天:
早上 git pull 拉下更新,git checkout -b 新分支,开始折腾新功能。
中午功能写了一半,git add .,git commit -m "WIP: 登录模块",先存到本地仓库,吃饭去。
下午在地铁上(没网),突然想到个优化方案,掏出笔记本继续写,git commit,完事。
晚上到家 push 到 GitHub,一气呵成。
到底选哪个?
SVN 还值得用的情况:
-
你们项目在管大量二进制文件,比如游戏里的美术资源、视频素材,Git 对这种文件不太友好
-
公司要求严格的目录级权限,比如财务代码只有财务组能看
-
团队就几个人,网络永远稳定,不想折腾学习成本
Git 更适合的情况:
-
写代码为主的项目(绝大部分情况)
-
想频繁开分支试想法,不怕搞乱
-
经常需要离线工作,或者参与开源项目
-
担心数据安全,想要多点备份
1.2 Git的传奇诞生
同生活中的许多伟大事物一样,Git诞生于一个极富纷争大举创新的年代。
Linux内核开源项目有着为数众多的参与者。绝大多数的Linux内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991一2002年间)。到2002年,整个项目组开始启用一个专有的分布式版本控制系统BitKeeper来管理和维护代码。
到了2005年,开发BitKeeper的商业公司同Linux内核开源社区的合作关系结束,他们收回了Linux内核社区免费使用BitKeeper的权力。这就迫使Linux开源社区(特别是Linux的缔造者Linus
Torvalds)基于使用BitKeeper时的经验教训,开发出自己的版本系统。他们对新的系统制订了若干 目标:
- 速度
- 简单的设计
对非线性开发模式的强力支持(允许成千上万个并行开发的分支) - 完全分布式
- 有能力高效管理类似Linux内核一样的超大规模项目(速度和数据量)
自诞生于2005年以来,Git日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。它的速度飞快,极其适合管理大项目,有着令人难以置信的非线性分支管理系统。
1.3 Git环境搭建指南
bash
yum install git/apt install git
6-4在Github创建项目
注册账号
这个比较简单,参考着官网提示即可.需要进行邮箱校验
创建项目
1.登陆成功后,进入个人主页,点击左下方的Newrepository按钮新建项目

2/然后跳转到的新页面中输入项目名称(注意,名称不能重复,系统会自动校验,校验过程可能会花费几秒钟).校验完毕后,点击下方的Createrepository按钮确认创建

3.在创建好的项目页面中复制项目的链接,以备接下来进行下载,

下载项目到本地
创建好一个放置代码的目录。
bash
git clone [url]
这里的url就是刚刚建立好的项目的链接.
1.4 Git核心三板斧
1.git add
将代码放到刚才下载好的目录中
bash
git add [⽂件名]
将需要用git管理的文件告知 git
2.git commit
提交改动到本地
bash
git commit -m "XXX"
最后的"."表示当前目录
提交的时候应该注明提交日志,描述改动的详细内容,
3.git push
同步到远端服务器上
bash
git push
需要填入用户名密码.同步成功后,刷新Github页面就能看到代码改动了.
配置免密码提交 :配置免密码提交
其他
-
git log/status/pull
-
ignore
-
首次使用问题,需要现场看
-
📥
git add:将文件添加到暂存区 -
📝
git commit:提交本地仓库 -
🚀
git push:同步到远程仓库
二、调试利器:GDB/CGDB实战指南
cpp
// mycmd.c
#include <stdio.h>
int Sum(int s, int e)
{
int result = 0;
for(int i = s; i <= e; i++)
{
result += i;
}
return result;
}
int main()
{
int start = 1;
int end = 100;
printf("I will begin\n");
int n = Sum(start, end);
printf("running done, result is: [%d-%d]=%d\n", start, end, n);
return 0;
}
- 程序的发布方式有两种,
debug模式和release模式,Linuxgcc/g++出来的二进制程序,默认是release模式。 - 要使用gdb调试,必须在源代码生成二进制程序的时候,加上
-g选项,如果没有添加,程序无法被编译
bash
$ gcc mycmd.c - o mycmd - g选项,如果没有添加,程序⽆法被
# 默认模式,不⽀持调试
$ file mycmd
mycmd : ELF 64 - bit LSB shared object, x86 - 64, version 1 (SYSV), dynamically
linked, interpreter / lib64 / ld - linux - x86 - 64.so.2,
BuildID[sha1] = 82f5cbaada10a9987d9f325384861a88d278b160, for GNU / Linux
3.2.0, not stripped
$ gcc mycmd.c - o mycmd - g
# debug模式
$ file mycmd
mycmd : ELF 64 - bit LSB shared object, x86 - 64, version 1 (SYSV), dynamically
linked, interpreter / lib64 / ld - linux - x86 - 64.so.2,
BuildID[sha1] = 3d5a2317809ef86c7827e9199cfefa622e3c187f, for GNU / Linux
3.2.0,
with debug_info, not stripped
2.1 基础调试流程
开始:gdb binFile
退出:ctrl + d或quit调试命令
bash
# 启动GDB调试程序
gdb <executable>
# 启动CGDB调试程序
cgdb <executable>
# 退出调试会话
quit # 或 q
程序运行控制
bash
# 启动程序
run <args> # 或 r <args>
# 继续执行程序
continue # 或 c
# 单步执行(不进入函数)
next # 或 n
# 单步执行(进入函数)
step # 或 s
# 跳出当前函数
finish
# 执行到当前函数返回
return
断点管理
设置断点
bash
# 在指定行设置断点
break <filename>:<line> # 或 b <filename>:<line>
# 在指定函数设置断点
break <function> # 或 b <function>
# 设置条件断点
break <location> if <condition>
# 设置临时断点(只触发一次)
tbreak <location>
管理断点
bash
# 查看所有断点
info breakpoints # 或 info b
# 启用/禁用断点
enable <breakpoint_number>
disable <breakpoint_number>
# 删除断点
delete <breakpoint_number> # 或 d <breakpoint_number>
# 清除指定位置的断点
clear <location>
变量与内存查看
查看变量
bash
# 打印变量值
print <var> # 或 p <var>
# 设置变量值
set var <var>=<value>
# 查看变量类型
whatis <var>
# 查看变量详细类型信息
ptype <var>
内存查看
bash
# 查看内存内容
x/<n/f/u> <address>
# n: 显示的单元数
# f: 显示格式(x-十六进制, d-十进制, u-无符号十进制, o-八进制, t-二进制, a-地址, i-指令, c-字符, s-字符串)
# u: 每个单元的大小(b-字节, h-半字, w-字, g-双字)
高级调试技巧
观察点(Watchpoint)
bash
# 设置观察点,当变量值变化时暂停
watch <var>
# 设置观察点,当变量被读时暂停
rwatch <var>
# 设置观察点,当变量被读写时暂停
awatch <var>
# 查看所有观察点
info watchpoints
调用栈查看
bash
# 查看调用栈
backtrace # 或 bt
# 查看指定层数的调用栈
backtrace <n>
# 切换栈帧
frame <frame_number>
# 查看当前栈帧信息
info frame
线程调试
bash
# 查看所有线程
info threads
# 切换到指定线程
thread <thread_number>
# 设置线程断点
break <location> thread <thread_number>
调试信息查看
查看源代码
bash
# 显示当前行附近的源代码
list # 或 l
# 显示指定函数的源代码
list <function>
# 显示指定行附近的源代码
list <filename>:<line>
查看寄存器
bash
# 查看所有寄存器
info registers
# 查看指定寄存器
info registers <regname>
实用快捷键
常用快捷键
Ctrl + C:中断程序执行Tab:命令补全Up/Down:历史命令切换Ctrl + D:退出GDB(等价于quit)
CGDB专属快捷键
F5:运行/继续F6:单步执行(next)F7:单步执行(step)F8:跳出当前函数(finish)Ctrl + Space:切换源代码和GDB命令窗口n:单步执行(next)s:单步执行(step)c:继续执行b:设置断点

2.2 高级调试技巧
2.2.1 Watch命令:变量变化监视器
执行时监视⼀个表达式(如变量)的值。如果监视的表达式在程序运行期间的值发生变化,GDB会暂停程序的执行,并通知使用者
-
📌 注意:
如果你有一些变量不应该修改,但是你怀疑它修改导致了问题,你可以watch它,如果变化了,就会通知你。
2.2.2 set var:动态修改变量值
更改一下标志位,假设我们想得到+ -result
bash
// mycmd.c
#include <stdio.h>
int flag = 0; //
故意错误
//int flag = -1;
//int flag = 1;
int Sum(int s, int e)
{
int result = 0;
for(int i = s; i <= e; i++)
{
result += i;
}
return result*flag;
}
int main()
{
int start = 1;
int end = 100;
printf("I will begin\n");
int n = Sum(start, end);
printf("running done, result is: [%d-%d]=%d\n", start, end, n);
return 0;
}
(gdb) l main
15
16
17
18
19
}
return result*flag;
int main()
20
21
22
23
24
{
int start = 1;
int end = 100;
printf("I will begin\n");
int n = Sum(start, end);
(gdb) b 24
Breakpoint 1 at 0x11ca: file mycmd.c, line 24.
(gdb) r
Starting program: /home/whb/test/test/mycmd
I will begin
Breakpoint 1, main () at mycmd.c:24
24
int n = Sum(start, end);
(gdb) n
25
printf("running done, result is: [%d-%d]=%d\n", start, end,
n);
(gdb) n
running done, result is: [1-100]=0
26
return 0;
#
这⾥结果为什么是
0
?
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /home/whb/test/test/mycmd
I will begin
Breakpoint 1, main () at mycmd.c:24
24
int n = Sum(start, end);
(gdb) s
Sum (s=32767, e=-7136) at mycmd.c:9
9
{
(gdb) n
10
(gdb) n
11
(gdb)
13
(gdb)
11
(gdb)
13
int result = 0;
for(int i = s; i <= e; i++)
result += i;
for(int i = s; i <= e; i++)
result += i;
(gdb) until 14
Sum (s=1, e=100) at mycmd.c:16
16
return result*flag;
(gdb) p result
$1 = 5050
(gdb) p flag
$2 = 0
(gdb) set var flag=1
#
更改
flag
的值,确认是否是它的原因
(gdb) p flag
$3 = 1
(gdb) n
17
}
(gdb) n
main () at mycmd.c:2
25
printf("running done, result is: [%d-%d]=%d\n", start, end,
n);
(gdb) n
running done, result is: [1-100]=5050
26
return 0;
2.2.3 条件断点:精准控制调试流程
添加条件断点
bash
(gdb) l main
int main()
{
int start = 1;
int end = 100;
printf("I will begin\n");
int n = Sum(start, end);
(gdb)b 20
Breakpoint 1 at 0x11c3: file mycmd.c, line 20.
(gdb)r
Starting program : / home / whb / test / test / mycmd
I will begin
Breakpoint 1, main() at mycmd.c : 20
20
int n = Sum(start, end);
(gdb)s
Sum(s = 32767, e = -7136) at mycmd.c:5
5
{
(gdb)n
6
(gdb)n
7
(gdb)n
9
(gdb)
7
(gdb)
9
(gdb)
7
(gdb)
9
(gdb)
7
(gdb)
b 9
int result = 0;
for (int i = s; i <= e; i++)
result += i;
for (int i = s; i <= e; i++)
result += i;
for (int i = s; i <= e; i++)
result += i;
for (int i = s; i <= e; i++)
Breakpoint 2 at 0x555555555186: file mycmd.c, line 9.
(gdb)info b
Num
Type
1
breakpoint
Disp Enb Address
What
keep y 0x00005555555551c3 in main at mycmd.c:20
breakpoint already hit 1 time
2
breakpoint
keep y
(gdb) n
0x0000555555555186
Breakpoint 2, Sum(s = 1, e = 100) at mycmd.c:9
9
result += i;
(gdb)n
7
(gdb)n
for (int i = s; i <= e; i++)
Breakpoint 2, Sum(s = 1, e = 100) at mycmd.c:9
9
result += i;
(gdb)
condition
(gdb) info b
Num
Type
2
i ==30
i ==
Disp Enb Address
in Sum at mycmd.c:9
30
What
breakpoint
keep y
1
Ox00005555555551c3 in main at mycmd.c:20
breakpoint already hit 1 time
breakpoint
keep y
2
Ox0000555555555186 in Sum at mycmd.c:9
stop only if i==30
breakpoint already hit 2 times
(gdb) n
7
for(int i = S; i <= e; i++)
(gdb) n
9
result += i;
(gdb) c
Continuing.
Breakpoint 2, Sum (s=1, e=1oo) at mycmd.c:9
9
result += i;
(gdb) p_
$1=30
(gdb) p result
$2=435

注意:
- 条件断点添加常见两种方式:1. 新增 2. 给已有断点追加
- 注意两者的语法有区别,不要写错了。
- 新增:
b 行号/文件名:行号/函数名 if i == 30(条件)- 给已有断点追加:
condition 2 i == 30,其中2是已有断点编号,没有if
