C++ 工程实践(3):采用有利于版本管理的代码格式

C++代码可视化实战:使用cpp2dia自动生成UML类图 在软件工程领域,代码可视化是理解复杂系统架构、提升团队协作效率的关键技术。其核心原理是通过静态分析工具解析源代码,提取类、方法、属性等元素及其关系,构建抽象模型,再通过图形渲染引擎生成可视化图表。这项技术的核心价值在于将抽象的代码逻辑转化为直观的图形表示,极大降低了理解大型遗留系统或新项目架构的认知负荷,是进行架构评审、编写设计文档和新人培训的利器。典型的应用场景包括维护大型C++遗留系统、进行架构设计沟通以及自动化文档生成。本文聚焦于cpp2dia这一轻量级开源工具,它巧妙地利用CastXML/GCC-X 阅读详情

陈硕 (giantchen_AT_gmail)

Blog.youkuaiyun.com/Solstice

版本管理(version controlling)是每个程序员的基本技能,C++ 程序员也不例外。版本管理的基本功能之一是追踪代码变化,让你能清楚地知道代码是如何一步步变成现在的这个样子,以及每次 check-in 都具体改动了哪些内部。无论是传统的集中式版本管理工具,如 Subversion,还是新型的分布式管理工具,如 Git/Hg,比较两个版本(revision)的差异都是其基本功能,即俗称“做一下 diff”。

diff 的输出是个窥孔(peephole),它的上下文有限(diff –u 默认显示前后 3 行)。在做 code review 的时候,如果能凭这“一孔之见”就能发现代码改动有问题,那就再好也不过了。

 

C 和 C++ 都是自由格式的语言,代码中的换行符被当做 white space 来对待。(当然,我们说的是预处理(preprocess)之后的情况)。对编译器来说一模一样的代码可以有多种写法,比如

foo(1, 2, 3, 4);

和

foo(1,

    2,

    3,

    4);

词法分析的结果是一样的,语意也完全一样。

对人来说,这两种写法读起来不一样,对与版本管理工具来说,同样功能的修改造成的差异(diff)也往往不一样。所谓“有利于版本管理”,就是指在代码中合理使用换行符,对 diff 工具友好,让 diff 的结果清晰明了地表达代码的改动。(diff 一般以行为单位,也可以以单词为单位,本文只考虑最常见的 diff by lines。)

这里举一些例子。

对 diff 友好的代码格式

1. 多行注释也用 //,不用 /* */

Scott Meyers 写的《Effective C++》第二版第 4 条建议使用 C++ 风格,我这里为他补充一条理由:对 diff 友好。比如,我要注释一大段代码(其实这不是个好的做法,但是在实践中有时会遇到),如果用 /* */,那么得到的 diff 是:

diff --git a/examples/asio/tutorial/timer5/timer.cc b/examples/asio/tutorial/timer5/timer.cc
--- a/examples/asio/tutorial/timer5/timer.cc
+++ b/examples/asio/tutorial/timer5/timer.cc
@@ -18,6 +18,7 @@ class Printer : boost::noncopyable
     loop2_->runAfter(1, boost::bind(&Printer::print2, this));
   }
+  /*
   ~Printer()
   {
     std::cout << "Final count is " << count_ << "/n";
@@ -38,6 +39,7 @@ class Printer : boost::noncopyable
       loop1_->quit();
     }
   }
+  */
   void print2()
   {

从这样的 diff output 能看出注释了哪些代码吗?

如果用 //,结果会清晰很多:

diff --git a/examples/asio/tutorial/timer5/timer.cc b/examples/asio/tutorial/timer5/timer.cc
--- a/examples/asio/tutorial/timer5/timer.cc
+++ b/examples/asio/tutorial/timer5/timer.cc
@@ -18,26 +18,26 @@ class Printer : boost::noncopyable
     loop2_->runAfter(1, boost::bind(&Printer::print2, this));
   }
-  ~Printer()
-  {
-    std::cout << "Final count is " << count_ << "/n";
-  }
+  // ~Printer()
+  // {
+  //   std::cout << "Final count is " << count_ << "/n";
+  // }
-  void print1()
-  {
-    muduo::MutexLockGuard lock(mutex_);
-    if (count_ < 10)
-    {
-      std::cout << "Timer 1: " << count_ << "/n";
-      ++count_;
-
-      loop1_->runAfter(1, boost::bind(&Printer::print1, this));
-    }
-    else
-    {
-      loop1_->quit();
-    }
-  }
+  // void print1()
+  // {
+  //   muduo::MutexLockGuard lock(mutex_);
+  //   if (count_ < 10)
+  //   {
+  //     std::cout << "Timer 1: " << count_ << "/n";
+  //     ++count_;
+  //
+  //     loop1_->runAfter(1, boost::bind(&Printer::print1, this));
+  //   }
+  //   else
+  //   {
+  //     loop1_->quit();
+  //   }
+  // }
   void print2()
   {

同样的道理,取消注释的时候 // 也比 /* */ 更清晰。

另外,如果用 /* */ 来做多行注释,从 diff 不一定能看出来你是在修改代码还是修改注释。比如以下 diff 似乎修改了 muduo::EventLoop::runAfter 的调用参数:

diff --git a/examples/asio/tutorial/timer5/timer.cc b/examples/asio/tutorial/timer5/timer.cc
--- a/examples/asio/tutorial/timer5/timer.cc
+++ b/examples/asio/tutorial/timer5/timer.cc
@@ -32,7 +32,7 @@ class Printer : boost::noncopyable
       std::cout << "Timer 1: " << count_ << "/n";
       ++count_;
-      loop1_->runAfter(1, boost::bind(&Printer::print1, this));
+      loop1_->runAfter(2, boost::bind(&Printer::print1, this));
     }
     else
     {

其实这个修改发生在注释里边 (要增加上下文才能看到, diff -U 20,多一道手续,降低了工作效率),对代码行为没有影响:

diff --git a/examples/asio/tutorial/timer5/timer.cc b/examples/asio/tutorial/timer5/timer.cc
--- a/examples/asio/tutorial/timer5/timer.cc
+++ b/examples/asio/tutorial/timer5/timer.cc
@@ -20,31 +20,31 @@ class Printer : boost::noncopyable
   /*
   ~Printer()
   {
     std::cout << "Final count is " << count_ << "/n";
   }

   void print1()
   {
     muduo::MutexLockGuard lock(mutex_);
     if (count_ < 10)
     {
       std::cout << "Timer 1: " << count_ << "/n";
       ++count_;
-      loop1_->runAfter(1, boost::bind(&Printer::print1, this));
+      loop1_->runAfter(2, boost::bind(&Printer::print1, this));
     }
     else
     {
       loop1_->quit();
     }
   }
   */
   void print2()
   {
     muduo::MutexLockGuard lock(mutex_);
     if (count_ < 10)
     {
       std::cout << "Timer 2: " << count_ << "/n";
       ++count_;

总之,不要用 /* */ 来注释多行代码。

或许是时过境迁,大家都在用 // 注释了,《Effective C++》第三版去掉了这一条建议。

2. 局部变量与成员变量的定义

基本原则是,一行代码只定义一个变量,比如

double x;

double y;

将来代码增加一个 double z 的时候,diff 输出一眼就能看出改了什么:

@@ -63,6 +63,7 @@ private:
   int count_;
   double x;
   double y;
+  double z;
 };

 int main()

如果把 x 和 y 写在一行,diff 的输出就得多看几眼才知道。

@@ -61,7 +61,7 @@ private:
   muduo::net::EventLoop* loop1_;
   muduo::net::EventLoop* loop2_;
   int count_;
-  double x, y;
+  double x, y, z;
 };

 int main()

所以,一行只定义一个变量更利于版本管理。同样的道理适用于 enum 成员的定义,数组的初始化列表等等。

3. 函数声明中的参数

如果函数的参数大于 3 个,那么在逗号后面换行,这样每个参数占一行,便于 diff。以 muduo::net::TcpClient 为例:

class TcpClient : boost::noncopyable
{
 public:
  TcpClient(EventLoop* loop,
            const InetAddress& serverAddr,
            const string& name);

如果将来 TcpClient 的构造函数增加或修改一个参数,那么很容易从 diff 看出来。这恐怕比在一行长代码里数逗号要高效一些。

4. 函数调用时的参数

在函数调用的时候,如果参数大于 3 个,那么把实参分行写。以 muduo::net::EPollPoller 为例:

Timestamp EPollPoller::poll(int timeoutMs, ChannelList* activeChannels)
{
  int numEvents = ::epoll_wait(epollfd_,
                               &*events_.begin(),
                               static_cast(events_.size()),
                               timeoutMs);
  Timestamp now(Timestamp::now());

这样一来,如果将来重构引入了一个新参数(好吧,epoll_wait 不会有这个问题),那么函数定义和函数调用的地方的 diff 具有相同的形式(比方说都是在倒数第二行加了一行内容),很容易肉眼验证有没有错位。如果参数写在一行里边,就得睁大眼睛数逗号了。

5. class 初始化列表的写法

同样的道理,class 初始化列表(initializer list)也遵循一行一个的原则,这样将来如果加入新的成员变量,那么两处(class 定义和 ctor 定义)的 diff 具有相同的形式,让错误无所遁形。以 muduo::net::Buffer 为例:

class Buffer : public muduo::copyable
{
 public:
  static const size_t kCheapPrepend = 8;
  static const size_t kInitialSize = 1024;

  Buffer()
    : buffer_(kCheapPrepend + kInitialSize),
      readerIndex_(kCheapPrepend),
      writerIndex_(kCheapPrepend)
  {
  }

  // 省略
 private:
   std::vector buffer_;
   size_t readerIndex_;
   size_t writerIndex_;

   static const char kCRLF[];
};

注意,初始化列表的顺序必须和数据成员声明的顺序相同。

6. 与 namespace 有关的缩进

Google 的 C++ 编程规范 明确指出,namespace 不增加缩进 。这么做非常有道理,方便 diff –p 把函数名显示在每个 diff chunk 的头上。

如果对函数实现做 diff,chunk name 是函数名,让人一眼就能看出改的是哪个函数。如下图,红色划线部分。

diff_function

如果对 class 做 diff,那么 chunk name 就是 class name。

diff_class

diff 原本是为 C 语言设计的,C 语言没有 namespace 缩进一说,所以它默认会找到“顶格写”的函数作为一个 diff chunk 的名字,如果函数名前面有空格,它就不认得了。muduo 的代码都遵循这一规则,例如:

namespace muduo
{
///
/// Time stamp in UTC, in microseconds resolution.
///
/// This class is immutable.
/// It's recommended to pass it by value, since it's passed in register on x64.
///
// class 从第一列开始写,不缩进
class Timestamp : public muduo::copyable,
                  public boost::less_than_comparable
{
// 函数的实现也从第一列开始写,不缩进。
Timestamp Timestamp::now()
{
  struct timeval tv;
  gettimeofday(&tv, NULL);
  int64_t seconds = tv.tv_sec;
  return Timestamp(seconds * kMicroSecondsPerSecond + tv.tv_usec);
}

相反,boost 中的某些库的代码是按 namespace 来缩进的,这样的话看 diff 往往不知道改动的是哪个 class 的哪个成员函数。

这个或许可以通过设置 diff 取函数名的正则表达式来解决,但是如果我们写代码的时候就注意把函数“顶格写”,那么就不用去动 diff 的默认设置了。另外,正则表达式不能完全匹配函数名,因为函数名是上下文无关语法(context-free syntax),你没办法写一个正则语法去匹配上下文无关语法。我总能写出某种函数声明,让你的正则表达式失效(想想函数的返回类型,它可能是一个非常复杂的东西,更别说参数了)。更何况 C++ 的语法是上下文相关的,比如你猜 Foo qux; 是个表达式还是变量定义?

7. public 与 private

我认为这是 C++ 语法的一个缺陷,如果我把一个成员函数从 public 区移到 private 区,那么从 diff 上看不出来我干了什么,例如:

diff --git a/muduo/net/TcpClient.h b/muduo/net/TcpClient.h
--- a/muduo/net/TcpClient.h
+++ b/muduo/net/TcpClient.h

@@ -37,7 +37,6 @@ class TcpClient : boost::noncopyable
   void connect();
   void disconnect();
-  bool retry() const;
   void enableRetry() { retry_ = true; }

   /// Set connection callback.
@@ -60,6 +59,7 @@ class TcpClient : boost::noncopyable
   void newConnection(int sockfd);
   /// Not thread safe, but in loop
   void removeConnection(const TcpConnectionPtr& conn);
+  bool retry() const;

   EventLoop* loop_;
   boost::scoped_ptr connector_; // avoid revealing Connector

从上面的 diff 能看出我把 retry() 变成 private 了吗?对此我也没有好的解决办法,总不能每个函数前面都写上 public: 或 private: 吧?

对此 Java 和 C# 都做得比较好,它们把 public/private 等修饰符放到每个成员函数的定义中。这么做增加了信息的冗余度,让 diff 的结果更直观。

 

 

8. 头文件的排列顺序

除了必须放在首位的头文件,其余的都按字典序排列。这样将多人修改时冲突的可能性降到最小。另外,Makefile 中的文件列表也按字典序排列,以降低冲突的可能。

参考 muduo/**/*.cc

 

 

欢迎补充。

对 grep 友好的代码风格

操作符重载

C++工具匮乏,在一个项目里,要找到一个函数的定义或许不算太难(最多就是分析一下重载和模板特化),但是要找到一个函数的使用就难多了。不比 Java,在 Eclipse 里 Ctrl+Shift+G 就能找到所有的引用点。

假如我要做一个重构,想先找到代码里所有用到 muduo::timeDifference 的地方,判断一下工作是否可行,基本上惟一的办法是grep。用 grep 还不能排除同名的函数和注释里的内容。这也说明为什么要用 // 来引导注释,因为在 grep 的时候,一眼就能看出这行代码是在注释里的。

在我看来,operator overloading 应仅限于和 STL algorithm/container 配合时使用,比如 transform() 和 map,其他情况都用具名函数为宜。原因之一是,我根本用 grep 找不到在哪儿用到了 operator-()。这也是 muduo::Timestamp 只提供 operator<() 而不提供 operator+() operator-() 的原因,我提供了两个函数 timeDifference 和 addTime 来实现所需的功能。

又比如,Google Protocol Buffers 的回调是 class Closure,它的接口用的是 virtual function Run() 而不是 virtual operator()()。

static_cast 与 C-style cast

为什么 C++ 要引入 static_cast 之类的转型操作符,原因之一就是像 (int*) pBuffer 这样的表达式基本上没办法用 grep 判断出它是个强制类型转换,写不出一个刚好只匹配类型转换的正则表达式。(again,语法是上下文无关的,无法用正则搞定。)

如果类型转换都用 *_cast,那只要 grep 一下我就能知道代码里哪儿用了 reinterpret_cast 转换,便于迅速地检查有没有用错。为了强调这一点,muduo 开启了编译选项 -Wold-style-cast 来帮助查找 C-style cast,这样在编译时就能帮我们找到问题。

 

 

一切为了效率

 

如果用图形化的文件比较工具,似乎能避免上面列举的问题。但无论是 web 还是客户端,无论是 inline diff 还是 diff by lines 都不能解决全部问题,效率也不一定更高。

 

对于(2),如果想知道是谁在什么时候增加的 double z,在分行写的情况下,用 git blame 或 svn blame 立刻就能找到始作俑者。如果写成一行,那就得把文件的 revisions 拿来一个个人工比较,因为这一行 double x = 0.0, y = 1.0, z = -1.0; 可能修改过多次,你得一个个看才知道什么时候加入了变量 z。这个 blame 的 case 也适用于 3、4、5。

 

比如(6)改动了一行代码,你还是要 scroll up 去找改的是哪个 function,人眼看的话还有“看走眼”的可能,又得再定睛观瞧。这一切都是浪费人的时间,使用更好的图形化工具并不能减少浪费,相反,我认为增加了浪费。

 

另外一个常见的工作场景,早上来到办公室,update 一下代码,然后扫一眼 diff output 看看别人昨天动了哪些文件,改了哪些代码,这就是一两条命令的事,几秒钟就能解决战斗。如果用图形化的工具,得一个个点开文件 diff 的链接或点开新 tab 来看文件的 side-by-side 比较(不这么做的话看不到足够多的上下文,跟看 diff output 无异),然后点击鼠标滚动页面去看别人到底改了什么。说实话我觉得这么做效率不比 diff 高。

 

(待续)

eie-c:C 语言版本控制系统 ie 用 C 编写的版本控制系统。 目前,这或多或少是我 3 年前编写的 eie Perl 脚本的克隆。 这个版本更快,更好,并且通常按照最初应该完成的方式完成。 但是,它可以以完全相同的方式使用。 特尔;博士: make make install 命令 eie init在工作目录中创建一个新的存储库。 eie add <file1> [file2]...将要提交的任意数量的文件排队。 eie clear清除要提交的文件列表。 eie commit提交加载的更改。 eie list <file>列出所有包含file的提交。 eie revert <file> <commit>将文件恢复到提交时的内容。 eie destroy删除 eie 信息文件夹并销毁存储库。 工作目录中的文件保持不变。 立即下载

相关推荐

工程实践第一部分

题目一 求两个集合的合并运算 编程实现两个集合的并运算,即A=A∪B,要求分别采用数组与链表实现,并分析两种方法各自的优缺点。

C++编程规范之3:使用版本控制系统

摘要:     好记性不如烂笔头;请使用版本控制系统(VCS)。永远不要让文件长时间地登出。在新的单元测试通过之后,应该频繁登入。确保登入的代码不会影响构建成功。 讨论:     几乎所有大一点的项目都需要不只一个开发人员和一周以上的开发时间。在这样的项目中,需要比较同一文件的各个历史版本,已确定修改是何时(以及/或者由谁)进行的;需要控制和管理源代码的变更。     如果有多个开发人员,

longshengguoji的专栏 3117

c++写的获取系统版本号的程序

c++写的获取系统版本号的程序,可以获取主版本和副版本号。

C++ 工程实践(9):数据抽象

陈硕 (giantchen_AT_gmail)http://blog.youkuaiyun.com/Solstice  http://weibo.com/giantchen陈硕关于 C++ 工程实践的系列文章: http://blog.youkuaiyun.com/Solstice/category/802325.aspx排版正常的版本: http://www.cnblogs.com/Solstice/category/...

weixin_33827590的博客 436

Python + Google AI 自动修复 Sonar Bug 实践

在工作中总会遇到种种不期而至的需求,比如前段时间突然要修复所有 Sonar Bug,涉及各种琐碎的代码风格问题,包括但不限于语法不规范、废弃注释等问题。这些项目都已经持续开发几年了,Sonar 上的问题层出不穷,各种问题已经累积到了上万个,手动修复不但费时费力,而且过程异常枯燥。

子洋 Blog 2416

旋转因子量化仿真

在单bit接收机中,通过量化FFT核函数来简化计算,将复数坐标中旋转因子单位圆上的点近似到正方形四个边上,就能够用累加实现复数乘法,从而避免使用DSP乘法器。通过仿真查看不同阶的核函数简化效果。

CyberInversion的博客 3103

C 语言常用的代码版本管理工具有哪些?C 语言的单元测试有哪些方法?C 语言的集成测试有哪些方法?C 语言的系统测试有哪些方法?

代码版本管理是指对软件开发过程中的源代码进行管理、追踪和控制的一种工具和方法。它可以帮助开发团队协同工作,追踪代码的变化,并提供回滚、分支管理等功能,以便更好地管理代码的演进。在C语言开发中,常用的代码版本管理工具有以下几种:Git: Git是目前最流行的分布式版本控制系统。它具有强大的分支管理功能,可以轻松地创建、合并和切换分支。Git还提供了方便的远程仓库管理和团队协作功能。Subversion (SVN): Subversion是一种集中式版本控制系统。

fengyege的博客 594

OCEAN 电路仿真

介绍利用 Cadence OCEAN 进行电路脚本仿真的方法

weixin_43249158的博客 4402

Microsoft Visual C++ 编译器版本控制

有用的两个例子是区分 Visual Studio 2019 16.8 与 16.9 以及 Visual Studio 2019 16.10 与 16.11。b Visual Studio 2019 16.10 和 16.11 共享相同的主版本和次要版本(因此具有相同的。a Visual Studio 2019 16.8 和 16.9 共享相同的主版本和次要版本(因此具有相同的。Visual Studio 2019 16.8 和 16.9 共享相同的主版本和次要版本,因此具有相同的。表示编译器的内部版本。

自学C++,会写HelloWorld 1356

【零基础学习CAPL】——CAN报文的发送(CRC——ChecSum值)

本章主要介绍带有CRC值的报文发送一般报文可使用CANoe的IG模块直接发送,但带有CRC值的报文就只能编写脚本进行发送,以下详细讲解在配合Panel面板情况下,如何进行带有循环冗余码的报文进行报文的发送。

LOVE135149的博客 1913

智元机器人开源具身仿真框架 Genie Sim 入门教程

GenieSim是AgiBot推出的具身智能仿真框架,提供完整闭环开发管道,包括轨迹生成、模型训练和基准测试。框架支持Docker容器化部署,要求Ubuntu 22.04和NVIDIA硬件环境。主要功能涵盖: 支持12种基准测试任务 提供ADER评估框架 集成UniVLA/RDT等基线模型 支持PICO/键盘遥操作 具备轨迹记录回放功能 提供完整API控制接口 开发者可通过Hugging Face获取场景资产,使用标准化ROS主题与仿真环境交互。框架特别优化了碰撞检测和抓取物理模拟,适用于从简单抓取到复杂操

2506_90492529的博客 2542

C++语言的版本控制

在C++项目中保持良好的版本控制实践,不仅能提高开发的效率,还有助于维护代码的整洁性和版本的可追溯性。选择合适的版本控制工具:根据项目规模和团队需求选择Git、SVN等适合的版本控制系统。维护一个清晰的目录结构:合理组织项目文件,分隔源代码、文档及资源文件。使用.gitignore保持版本库清洁:避免将临时文件、构建文件等不必要的文件纳入版本控制。制定规范的提交信息:确保每次提交都有清晰、描述性的提交信息,便于后期查阅。定期进行代码评审。

2501_91366458的博客 837

大数据运营可视化:Tableau+PowerBI数据看板搭建教程

当企业数据量从“GB级”跃升到“TB级”,运营人员面临的最大挑战不再是“如何收集数据”,而是“如何让数据说话”。数据看板作为“运营指挥中心的仪表盘”,能将零散的用户行为、业务指标、流量趋势浓缩成可快速解读的视觉语言。本文结合Tableau(自由探索的“数据瑞士军刀”)与PowerBI(微软生态的“组合家具”)两大工具,从需求分析→数据准备→指标设计→视图搭建→ dashboard 组合,一步步教你搭建符合业务逻辑的运营看板。

AI智能探索者的博客 1364

有利于版本管理的代码格式

版本管理(version controlling)是每个程序员的基本技能,C++ 程序员也不例外。版本管理的基本功能之一是追踪代码变化,让你能清楚地知道代码是如何一步步变成现在的这个样子,以及每次 check-in 都具体改动了哪些内部。无论是传统的集中式版本管理工具,如 Subversion,还是新型的分布式管理工具,如 Git/Hg,比较两个版本(revision)的差异都是其基本功能,即俗称“做一下 diff”。diff 的输出是个窥孔(peephole),它的上下文有限(diff –u 默认显示前后

小青年儿 1037

C盘在同一时间突然多出100g的文件,请问是什么原因,以及是否能删除?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1761

Attention优化|2w字原理&图解: 从Online-Softmax到FlashAttention V1/V2/V3

本文通过原理分析和图解的方式,通俗易懂地FlashAttention系列算法。FlashAttention V1/V2在LLM领域的应用已经非常广泛,相关的论文也反复读了几遍。FA1和FA2论文非常经典,都推荐读一下(不过FA2论文中公式错误不少本文大约1.8w字,包括以下内容:0x07 分布式训推使用FlashAttention0x09 FlashAttention中MQA/GQA以及Causal Mask处理。

具身机器人和通用AI 5232

采用有利于版本管理的代码格式

<br /><br />陈硕 (giantchen_AT_gmail)<br />Blog.youkuaiyun.com/Solstice<br />版本管理(version controlling)是每个程序员的基本技能,C++ 程序员也不例外。版本管理的基本功能之一是追踪代码变化,让你能清楚地知道代码是如何一步步变成现在的这个样子,以及每次 check-in 都具体改动了哪些内部。无论是传统的集中式版本管理工具,如 Subversion,还是新型的分布式管理工具,如 Git/Hg,比较两个版本(revision)的

痞子龙3D编程 1340

数学建模模型总结

这是本人在两年内准备参加数学建模竞赛搜集到的模型资料,已经经过了本人的筛选和整理,希望能够对准备参加数学建模竞赛的学弟学妹们提供一些帮助!

从开发到发布:whisper.cpp版本管理全流程解析

whisper.cpp是OpenAI的Whisper模型在C/C++中的移植版本,它提供了高性能的自动语音识别(ASR)功能。该项目具有无依赖的纯C/C++实现、跨平台支持以及多种硬件加速选项等特点。 项目的核心代码结构包含在[src/whisper.cpp](https://link.gitcode.com/i/d9e9899602bf2b0fe611ef495c53ec11)中,提供了C风格...

gitblog_01027的博客 877

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

llama.cpp模型版本管理:语义化版本控制

在大语言模型部署和推理的生态系统中,有效的版本管理是确保模型兼容性、可重现性和稳定性的关键因素。llama.cpp作为领先的C/C++大语言模型推理框架,采用了一套精心设计的语义化版本控制体系,为开发者提供了清晰的版本管理规范。 ## 版本控制体系架构 llama.cpp的版本控制系统采用多层级的架构设计,涵盖了框架版本、文件格式版本、量化版本等多个维度: ```mermaid flowch...

gitblog_00410的博客 388

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

第12章 C++经验谈(2)

我对C++的基本态度是“练从难处练,用从易处用”,因此本章有几节“负面”的内容。我坚信软件开发一定要时刻注意减少不必要的复杂度,一些花团锦簇的招式玩不好反倒会伤到自己。作为应用程序的开发者,对技术的运用要明智,不要为了解决难度系数为10的问题而去强攻难度系数为100的问题,这就本末倒置了。

chenyijun的专栏 1047

C++ 工程实践经验谈(转)

文章的排版不行,大家可以去http://cloud.github.com/downloads/chenshuo/documents/CppPractice.pdf 查看原文 C++ 工程实践经验谈 by 陈硕 1C++ 工程实践经验谈陈硕 (giantchen@gmail.com)最后更新 2012-4-1版权声明本作品采用“Creative Commons 署名 -非商业性使用 -禁止演绎 ...

weixin_34397291的博客 490

yaml-cpp语义版本管理终极指南:使用Git标签自动生成版本号

yaml-cpp作为一款强大的C++ YAML解析与生成库,其版本管理对于开发者协作和用户使用至关重要。本文将详细介绍如何通过Git标签实现yaml-cpp的语义化版本自动管理,帮助项目维护者更高效地进行版本控制。 ## 什么是语义化版本控制? 语义化版本(Semantic Versioning)采用`主版本号.次版本号.修订号`(MAJOR.MINOR.PATCH)的格式,其中: - **主

gitblog_00818的博客 671

C++工程实践经验

1 C++ 工程实践经验谈 陈硕 (giantchen@gmail.com) 最后更新 2012-4-20 版权声明 本作品采用“Creative Commons 署名 -非商业性使用 -禁止演绎 3.0 Unported 许可 协议 (cc by-nc-nd)”进行许可。http://creativecommons.org/licenses/by-nc-nd/3.0/ 内容

kamo54的专栏 1万+
上一篇: 为什么 muduo 的 shutdown() 没有直接关闭 TCP 连接?
下一篇: C++ 工程实践(4):二进制兼容性
陈硕
博客等级 码龄26年 1万+粉丝 144原创
评论 24
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值