网站首页图片大小,企业163邮箱怎么申请,网站营销队伍,百度权重高的网站有哪些点击蓝字关注我们因公众号更改推送规则#xff0c;请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络#xff0c;侵删C最佳实践:1. 工具2. 代码风格#xff08;本文#xff09;3. 安全性4. 可维护性5. 可移植性及多线程6. 性能7. 正确性和脚本代码风格代码风格最…点击蓝字关注我们因公众号更改推送规则请点“在看”并加“星标”第一时间获取精彩技术分享来源于网络侵删C最佳实践:1. 工具2. 代码风格本文3. 安全性4. 可维护性5. 可移植性及多线程6. 性能7. 正确性和脚本代码风格代码风格最重要的是一致性其次是遵循C程序员习惯的阅读风格。C允许任意长度的标识符名称因此在命名时没必要非要保持简洁建议使用描述性名称并在风格上保持一致。CamelCase(驼峰命名法)snake_case(蛇形命名法)这两种是很常见的命名规范snake_case的优点是在需要的时候可以适配拼写检查器。建立代码风格指南无论建立什么样的代码风格指南一定要实现指定期望风格的.clang-format文件。虽然这对命名没有帮助但对于开源项目来说保持一致的风格尤为重要。许多IDE、编辑器都支持内置的clang-format或者可以很方便的通过加载项安装。VSCode: Microsoft C/C extension for VS Code[2]CLion: ClangFormat as alternative formatterVisualStudio: ClangFormat[3]Resharper: Using Clang-Format[4]VimFormat your C family code[5]vim-autoformat[6]XCode: ClangFormat-Xcode[7]通用C命名约定类以大写字母开头: MyClass。函数和变量以小写字母开头: myMethod。常量全部大写: const double PI3.14159265358979323。C标准库(以及其他著名C库如Boost[8])使用以下指导原则:宏使用大写和下划线: INT_MAX。模板参数名使用驼峰命名法: InputIterator。所有其他名称都使用蛇形命名法: unordered_map。区分私有对象数据使用m_前缀命名私有数据以区别于公共数据m_代表“member成员”数据。区分函数参数最重要的是保持代码库的一致性这是一种有助于保持一致性的方式。使用t_前缀命名函数参数t_可以被认为是“the”但其可以表示任意含义关键是要将函数参数与作用域内的其他变量区分开来同时遵循一致的命名策略。可以为团队选择任何前缀或后缀下面是一个例子提出了一个有争议的建议相关讨论见issue #11[9]。struct Size
{int width;int height;Size(int t_width, int t_height) : width(t_width), height(t_height) {}
};// This version might make sense for thread safety or something,
// but more to the point, sometimes we need to hide data, sometimes we dont.
class PrivateSize
{public:int width() const { return m_width; }int height() const { return m_height; }PrivateSize(int t_width, int t_height) : m_width(t_width), m_height(t_height) {}private:int m_width;int m_height;
};不要用下划线(_)作为名字的开头_ 开头的名字有可能与编译器或标准库的保留名发生冲突: What are the rules about using an underscore in a C identifier?[10]良好代码风格示例class MyClass
{
public:MyClass(int t_data): m_data(t_data){}int getData() const{return m_data;}private:int m_data;
};使Out-of-Source-Directory构建确保构建生成的文件存放在与源文件夹分离的输出文件夹中。使用nullptrC11引入了nullptr表示空指针应该用来代替0或NULL来指示空指针。注释注释块应该使用//而不是/* */使用//可以更容易的在调试时注释掉代码块。// this function does something
int myFunc()
{
}要在调试期间注释掉这个函数块可以这样做:/*
// this function does something
int myFunc()
{
}
*/如果函数头注释使用/* */这么做就会有冲突。永远不要在头文件中使用using namespace这会导致正在using的命名空间被强行拉入到包含头文件的所有文件的命名空间中从而造成命名空间污染并可能在导致名称冲突。在实现文件中using命名空间就足够了。Include保护符头文件必须包含名称清晰的include保护符从而避免同一头文件被多次include的问题并防止与其他项目的头文件发生冲突。#ifndef MYPROJECT_MYCLASS_HPP
#define MYPROJECT_MYCLASS_HPPnamespace MyProject {class MyClass {};
}#endif此外还可以考虑使用#pragma once指令这是许多编译器的准标准内容简短意图明确。代码块必须包含{}省略{}可能会导致代码语义错误。// Bad Idea
// This compiles and does what you want, but can lead to confusing
// errors if modification are made in the future and close attention
// is not paid.
for (int i 0; i 15; i)std::cout i std::endl;// Bad Idea
// The cout is not part of the loop in this case even though it appears to be.
int sum 0;
for (int i 0; i 15; i)sum;std::cout i std::endl;// Good Idea
// Its clear which statements are part of the loop (or if block, or whatever).
int sum 0;
for (int i 0; i 15; i) {sum;std::cout i std::endl;
}保持每行代码长度合理// Bad Idea
// hard to follow
if (x y myFunctionThatReturnsBool() caseNumber3 (15 12 || 2 3)) {
}// Good Idea
// Logical grouping, easier to read
if (x y myFunctionThatReturnsBool() caseNumber3 (15 12 || 2 3)) {
}许多项目和编码标准都对此制定了软规则即每行字符应该少于80或100个这样的代码通常更容易阅读此外还可以把两个文件并排显示在一个屏幕上不用小字体也能看到全部代码。使用表示include本地文件...表示include系统文件[11]。// Bad Idea. Requires extra -I directives to the compiler
// and goes against standards.
#include string
#include includes/MyHeader.hpp// Worse Idea
// Requires potentially even more specific -I directives and
// makes code more difficult to package and distribute.
#include string
#include MyHeader.hpp// Good Idea
// Requires no extra params and notifies the user that the file
// is a local file.
#include string
#include MyHeader.hpp初始化成员变量...使用成员初始化列表。对于POD类型初始化列表的性能与手动初始化相同但对于其他类型有明显的性能提升见下文。// Bad Idea
class MyClass
{
public:MyClass(int t_value){m_value t_value;}private:int m_value;
};// Bad Idea
// This leads to an additional constructor call for m_myOtherClass
// before the assignment.
class MyClass
{
public:MyClass(MyOtherClass t_myOtherClass){m_myOtherClass t_myOtherClass;}private:MyOtherClass m_myOtherClass;
};// Good Idea
// There is no performance gain here but the code is cleaner.
class MyClass
{
public:MyClass(int t_value): m_value(t_value){}private:int m_value;
};// Good Idea
// The default constructor for m_myOtherClass is never called here, so
// there is a performance gain if MyOtherClass is not is_trivially_default_constructible.
class MyClass
{
public:MyClass(MyOtherClass t_myOtherClass): m_myOtherClass(t_myOtherClass){}private:MyOtherClass m_myOtherClass;
};在C11中可以为每个成员初始化默认值(使用或使用{})。使用设置默认值// ... //
private:int m_value 0; // allowedunsigned m_value_2 -1; // narrowing from signed to unsigned allowed
// ... //这样可以确保不会出现构造函数“忘记”初始化成员对象的情况。用大括号初始化默认值用大括号初始化不允许在编译时截断数据长度。// Best Idea// ... //
private:int m_value{ 0 }; // allowedunsigned m_value_2 { -1 }; // narrowing from signed to unsigned not allowed, leads to a compile time error
// ... //除非有明确的理由否则优先使用{}初始化而不是。忘记初始化成员会导致未定义行为错误而这些错误通常很难发现。如果成员变量在初始化后不会更改则将其标记为const。class MyClass
{
public:MyClass(int t_value): m_value{t_value}{}private:const int m_value{0};
};由于不能给const成员变量赋值拷贝赋值操作可能对这样的类没有意义。总是使用命名空间几乎没有理由需要全局命名空间中声明标识符。相反函数和类应该存在于适当命名的命名空间中或者存在于命名空间里的类中。放在全局命名空间中的标识符有可能与来自其他库(主要是没有命名空间的C库)的标识符发生冲突。为标准库特性使用正确的整数类型标准库通常使用std::size_t来处理与尺寸相关的内容size_t的大小由实现定义。一般来说使用auto可以避免大部分问题。请确保使用正确的整数类型并与C标准库保持一致否则有可能在当前使用的平台上不会发出警告但如果切换到其他平台可能会发出警告。注意在对无符号数执行某些操作时可能会导致整数下溢。例如:std::vectorint v1{2,3,4,5,6,7,8,9};
std::vectorint v2{9,8,7,6,5,4,3,2,1};
const auto s1 v1.size();
const auto s2 v2.size();
const auto diff s1 - s2; // diff underflows to a very large number使用.hpp和.cpp作为文件扩展名归根结底这是个人喜好问题但是.hpp和.cpp已被各种编辑器和工具广泛认可。因此这是一个务实的选择。具体来说Visual Studio只自动识别.cpp和.cxx为C文件而Vim不一定会把.cc识别为C文件。某个特别大的项目(OpenStudio[12])使用.hpp和.cpp表示用户生成的文件而使用.hxx和.cxx表示工具生成的文件。两者都能被很好的识别并且区分开来有很大的帮助。不要混用tab和空格某些编辑器喜欢在默认情况下使用tab和空格的混合缩进这使得没有使用完全相同的tab缩进设置的人很难阅读代码。请配置好编辑器确保不会发生这种情况。不要将有副作用的代码放在assert()中assert(registerSomeThing()); // make sure that registerSomeThing() returns true上述代码在debug模式下构建时可以成功运行但在进行release构建时会被编译器删除从而造成debug和release构建的行为不一致原因在于assert()是一个宏它在release模式下展开为空。不要害怕模板模板可以帮助我们坚持DRY原则[13]。由于宏有不遵守命名空间等问题因此能用模板的地方就不要用宏。明智的使用操作符重载运算符重载是为了支持表达性语法。比如让两个大数相加看起来像a b而不是a.add(b)。另一个常见的例子是std::string通常使用string1 string2连接两个字符串。但是使用过多或错误的操作符重载很容易写出可读性不强的表达式。在重载操作符时要遵循stackoverflow文章[14]中描述的三条基本规则。具体来说记住以下几点:处理资源时必须重载operator()参见下面Rule of Zero章节。对于所有其他操作符通常只有在需要在上下文中使用时才重载。典型的场景是用连接事物负号可以被认为是“真”或“假”的表达式等等。一定要注意操作符优先级[15]尽量避免不直观的结构。除非实现数字类型或遵循特定域中可识别的语法否则不要重载~或%这样的外部操作符。永远不要重载operator,()[16](逗号操作符)。处理流时使用非成员函数operator()和operator()。例如可以重载operator(std::ostream MyClass const )从而允许将类“写入”到一个流中例如std::cout或std::fstream或std::stringstream后者通常用于创建值的字符串表示。这篇文章描述了更多需要重载的常见操作符: What are the basic rules and idioms for operator overloading?[17]。更多关于自定义操作符实现细节的技巧可以参考: C Operator Overloading Guidelines[18]。避免隐式转换单参数构造函数可以在编译时应用单参数构造函数在类型之间自动转换比如像std::string(const char *)这样的转换很方便但通常应该避免因为可能会增加额外的运行时开销。相反可以将单参数构造函数标记为explicit从而要求显式调用。转换操作符与单参数构造函数类似编译器可以调用转换操作符同样也会引入额外开销也应该被标记为explicit。//bad idea
struct S {operator int() {return 2;}
};//good idea
struct S {explicit operator int() {return 2;}
};考虑Rule of ZeroRule of Zero规定除非所构造的类具有某种新的所有权形式否则不提供编译器可以提供的任何函数(拷贝构造函数、拷贝赋值操作符、移动构造函数、移动赋值操作符、析构函数)。目标是让编译器提供在添加更多成员变量时自动维护的最佳版本。这篇文章介绍了这一原则的背景并解释了几乎可以覆盖所有情况的实现技术: Cs Rule of Zero[19]。如果你年满18周岁以上又觉得学【C语言】太难想尝试其他编程语言那么我推荐你学Python现有价值499元Python零基础课程限时免费领取限10个名额▲扫描二维码-免费领取戳“阅读原文”我们一起进步