参考的Andrey Karpov的<<C++编程的 42 条建议>>
这里可能有一些老古董的东西,仅供参考
1.不要复制粘贴太多相同的语句,要学会用循环解决问题。
例子:
int tem(string a, char b) {
if (a[0] != b) return a[0] - b;
if (a[1] != b) return a[1] - b;
if (a[2] != b) return a[0] - b;
if (a[3] != b) return a[3] - b;
if (a[4] != b) return a[4] - b;
if (a[5] != b) return a[5] - b;
if (a[6] != b) return a[6] - b;
}
很明显因为复制粘贴的缘故,出现了一个错误:
if (a[2] != b) return a[0] - b;
这里并没有把a[0],改成a[2]。
如果我们用循环写:
int tem(string a, char b) {
for (int i = 0; i < 6; i++) {
if (a[i] != b) return a[i] - b;
}
}
代码准确率高,写起来又轻松。
2.大于 0 并不意味着是 1。
例子:
函数:int memcmp(const void *str1, const void *str2, size_t n)
参数:
str1 – 指向内存块的指针。
str2 – 指向内存块的指针。
n – 要被比较的字节数。
头函数:#include<memory.h>
返回值:
如果返回值 < 0,则表示 str1 小于 str2。
如果返回值 > 0,则表示 str2 小于 str1。
如果返回值 = 0,则表示 str1 等于 str2。
作用:把存储区 str1 和存储区 str2 的前 n 个字节进行比较。
有很多函数返回值<0的时候就返回-1,但是不是所有的函数都这样,比如上面我们介绍的这一个memcmp。
例子:
void testMemory() {
char str1[10];
char str2[10];
int ret;
memcpy(str1, "abcdef", 6);
memcpy(str2, "ABCDEF", 6);
ret = memcmp(str1, str2, 5);
if (ret == 1) {
printf("str1大");
}
if (ret == -1) {
printf("str2大");
}
if (ret == 0) {
printf("一样大");
}
}
先说明一下,这个程序在VS2019和某些编译器上是能正常跑的,但是这完全不是这个函数原本的意思我们需要把ret== 1改成ret>0,把ret== -1该成ret<0。
void testMemory() {
char str1[10];
char str2[10];
int ret;
memcpy(str1, "abcdef", 6);
memcpy(str2, "ABCDEF", 6);
ret = memcmp(str1, str2, 5);
if (ret > 0) {
printf("str1大");
}
if (ret < 0) {
printf("str2大");
}
if (ret == 0) {
printf("一样大");
}
}
3.注意字符优先级,在不清楚的情况下,尽量都用括号括起来。
图是从百度百科直接拿来的。图的来源链接
例子:
如果你想判断b是否等于1如果b等于1那么,a就加1。
void testPriority() {
int a = 5;
bool b = 0;
cout << (a + (b) ? 1 : 0);
}
这样明显是不对的,.三元运算符“?:”的优先级非常低,比“/,+,<”这些运算符的优先级要低。
可以加括号这么用:
void testPriority() {
int a = 5;
bool b = 0;
cout << (a + (b ? 1 : 0));
}
4.不要在循环里开辟一块临时缓冲区。
例子:
alloca()函数使用栈来分配内存。由 alloca() 分配到的内存要在函数执行完的时候才被回收。分给程序的栈内存通常不多当你在 Visual C++里新建一个项目的时候,你可以看到默认就分配1MB 的栈内存,这也就是为什么如果 alloca()函数是在循环里的话,它很快就把可用的栈内存耗光了。
解决问题的方案:
- 提前申请内存,然后使用一个缓冲区来进行所有操作。如果你每次需要的缓冲区大小。都不一样,申请最大块的内存。如果不行(你不知道具体它会需要多少内存),使用法2。
- 把循环单独放在一个函数里。这样,缓冲区在每一次迭代的时候都会申请、释放。如果这样也不行,就只剩下法 3 了。
- 用 malloc()或者其他操作来代替 alloca(),或者使用类似 std::vector 函数。但是这样分配内存会比较耗时间。如果使用 malloc/new 你就要考虑到它的释放。另一方面,这样你在用比较大的数给客户演示程序的时候不会发生栈溢出。
5.尽量让析构函数简单。
析构函数可以在异常发生后在清理栈内存的过程中调用,也可以在对象生命周期结束时被调用。如果在异常已经发生时调用析构函数,而在清理栈内存时又发生异常,异常之上又有异常,C++库只能调用 terminate()来终止程序。所以从中我们可以知道,不要在析构函数中抛 出异常。析构函数中的异常要在同一个析构函数中处理。
所以说,咱们的析构函数要尽可能简单。
6.用字符表示符号。
可以用下面的符号:
- 0——整数 0;
- nullptr——C++中的空指针;
- NULL——C 中的空指针 ;
- ‘\0’, L’\0’, _T(’\0’)——空结束;
- 0.0, 0.0f——浮点类型的0 ;
- false,FALSE——’false‘值;
7.不要把所有的操作运算都压缩到同一行。
当然,我个人也遇见过一行流天才型选手= =,因为我是个咸鱼,就不喜欢一行流。
例:
int testOneLine(int a,int b,int c,int d) {
return ((b = b - a / c) + c + d - (a = a * b + c));
}
这样做并不会帮到编译器半分,但是在让代码更难懂,让其他程序员(甚至作者自己)更难 理解上面确实起了不少作用。而且这种代码出错的概率也比较高。
所以说可以这么写:
int testOneLine(int a,int b,int c,int d) {
b = b - a / c;
a = a * b + c;
return (b + c + d - a);
}
这样就好多了。
8.设计表格风格的格式。
例子:(这个是我扒的《C++编程的 42 条建议》的代码。
void adns__querysend_tcp(adns_query qu, struct timeval now) {
...
if (!(errno == EAGAIN || EWOULDBLOCK ||
errno == EINTR || errno == ENOSPC ||
errno == ENOBUFS || errno == ENOMEM)) {
...
}
很明显,代码很乱看起来不容易而且这个代码少了一个errno。
所以可以改一下:
if (!(errno == EAGAIN || errno == EWOULDBLOCK ||
errno == EINTR || errno == ENOSPC ||
errno == ENOBUFS || errno == ENOMEM))
但是还是看起来不舒服
再改一下,可以把符号放了前面
if (!( errno == EAGAIN
|| EWOULDBLOCK
|| errno == EINTR
|| errno == ENOSPC
|| errno == ENOBUFS
|| errno == ENOMEM))
9.开始在你的代码中使用 enum class。
在标准C++中,枚举类型不是类型安全的。枚举类型被视为整数,这使得两种不同的枚举类型之间可以进行比较。
例子:
enum class Enumeration1
{
Val1, // 0
Val2, // 1
Val3 = 100,
Val4 /* = 101 */
};
//指定类型:
int main(int argc, char** argv)
{
Enumeration1 my=Enumeration1::Val3;
cout<<static_cast<int>(my)<<endl;
cout<<static_cast<double>(Enumeration2::val2)<<endl;
return 0;
}
10.如何正确的从一个构造函数中调用另一个。
class A {
int b;
A(int a) {
A();
}
};
构造函数这一块,很多人在想要使代码更简短有条理时会自食其果。你知道,构造函数和普通函数不一样。如果我们写"A::A(int x) { A(); }",会创建一个临时的无名的 A 类对象,而不是调用一个无参的构造函数。这也是上面的代码所发生的:创建一个临时的无名Guess()对象,然后即刻被摧毁,因为没有初始化函数成员。
有三种方法可以避免在构造函数中写重复代码。
1.第一种方法,设计一个独立的初始化函数,然后在两个构造函数里调用它。
2.第二种方法:
A::A(int b)
{
new (this) A();
....
}
3.第三种方法
A::A(int b)
{
this->A();
....
}
第二种和第三种方法比较危险是因为基类被初始化了两次。这样的代码会引起一些不明显的
bug,有害而无益。
建议:
C++11 允许在构造函数中调用同类的另一个构造函数(也就是委托「delegation」)。
这样就一个构造函数只要用几行代码就可以使用另一个构造函数的行为了。
一个类的成员变量里面有另外一个类的指针,它的优点是可以跟别人共享一份资源。并且调用和实现不在一起。叫做编译防火墙。
委托代码:
A::A(int b) :A()
{
....
}
实列:
#include <iostream>
using namespace std;
class stringRep
{
friend class sstring;
public:
stringRep():count_n(0){cout<<"construct a objectStringRep"<<endl;}
~stringRep(){cout<<"destruct a objectStringRep"<<endl;}
stringRep(char* chr):count_n(0){this->chr=chr;cout<<"construct a objectStringRep"<<endl;}
char* getchr() const {return chr;}
int get_n() const {return count_n;}
private:
int count_n;
char* chr;
};
class sstring
{
public:
sstring(){cout<<"construct a objectSTRING"<<endl;}
sstring(stringRep& sr):rep(&sr){sr.count_n++;cout<<"construct a objectSTRING"<<endl;}
~sstring(){(rep->count_n)--;cout<<"construct a objectString"<<endl;}
private:
stringRep* rep;
};
调用:
#include <iostream>
#include "delegation.h"
using namespace std;
int main()
{
stringRep sr("乌兹 never give up!!!");
sstring str1(sr);
sstring str2(sr);
sstring str3(sr);
cout<<endl<<"count_n="<<sr.get_n()<<endl<<endl;
cout<<sr.getchr()<<endl<<endl;
return 0;
}
以上委托参考的 一只鱼鱼鱼的博客。传送门