C++

萌新码代码的注意事项

Posted by Hazuki on 2019-03-01

我一直认为,码代码时有一些强迫症是比较好的,这样写出来的代码干净整洁,至少看着就很舒服。在学习的过程中,我也见到、听到了不少在编程时要注意的小习惯,把它们整理一下,虽然不一定全面,但是我觉得还是可以参考的。

头文件的防卫式声明

在编写头文件时,使用这样的格式

1
2
3
4
5
6
7
#ifndef __FILENAME__
#define __FILENAME__

...


#endif

这应该算是头文件的一种标准格式了。使用这种防卫式声明的好处是,别人在使用这个头文件时,不用考虑是否重复包含了头文件,或者包含头文件的顺序。如果头文件 a.h 没有这种防卫式声明,而 b.h 中包含了 a.h ,那么在程序中同时包含这两个头文件,在编译时就会出错。

头文件中不要使用 using

我最开始接触 C++ 的时候是高一,那时候每次写程序一上来就是固定的

1
2
3
#include <iostream>
using namespace std;
int main(){return 0;}

但是并不知道每条语句的含义。直到大学看了一些书,才明白输入输出流、命名空间和主函数。但是这个习惯也一直保持到了现在……因为自己基本上都是使用标准的命名空间,所以写头文件时因为懒得写 std::string 就会直接在开头写上 using namespace std;

不过,后来在看书时发现,不要在头文件中使用 using 这句警告,因为如果在头文件中使用了 using,甚至直接引入标准命名空间的所有内容,在别人使用自己的命名空间的时候,可能会产生冲突,到时候真的是写程序一时爽,查BUG火葬场了……

所以说,偷懒真的不是什么好事情……

使用构造函数初始值

假设有一个 complex 类,下面这两个构造函数完成的功能是一样的,但是上面的代码效率更高。前者直接初始化数据成员,后者则先初始化再赋值。

1
2
3
4
5
6
7
8
9
complex (double r = 0, double i = 0) // 默认实参
: re (r), im (i) //初值列,初始列
{} // 没有返回值类型

complex (double r = 0double i = 0)
{
re = r;
im = i;
}

注意常量成员函数

我们仍然使用上面的 complex 类举例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{}
double real() const {return re;}
double imag() const {return im;}
private:
double re, im;
}

//======
{
complex c1(2,1);
cout << c1.real() << " " << c1.imag() << endl;
const complex c2(2,1);
cout << c2.real() << " " << c2.imag() << endl;
}

注意 complex 的两个成员函数 real() 和 imag(),它们并没有改变参数的值,因此他们在小括号后添加了 const,表示调用该函数的对象是一个常量。

如果不使用 const,显然 c1 在调用两个函数时是没有问题的。但是 c2 在调用这两个函数时,由于 c2 是常量,是不能改变的,而编译器会认为这两个函数有可能会改变 c2 的值,这是不允许的,此时就会出错。

因此,在编写程序时,要多注意这一点。这样做有利于提高函数的灵活性。

参数传递和返回尽量传递引用

C++在传递参数时,如果是传递参数的值,那么该参数有多大,就会传递多大的数据。比如,传递一个 double 类型,是4字节,就会传递 4 个字节。当数据量很大时,参数传递的量就会很大,会影响程序的效率。而传递引用则相当于传递指针。

当你在函数中不想修改参数的值时,可以令其为 const,这样编译器就不允许函数修改该参数。

同样,在函数返回时,如果能返回引用则尽量返回引用。但是不一定所有的函数都能够返回引用。例如,在complex body 外定义如下函数:

1
2
3
4
5
6
7
8
9
10
11
12
inline complex&
__doapl (complex* ths, const complex& r)
{
ths->re += r.re; // 第一个参数将会被改动
ths->im += r.im; // 第二个参数不会被改动
return *ths;
}
inline complex&
complex::operator += (const complex& r)
{
return __doapl (this, r);
}

__doapl 函数返回了参数ths的引用。这样做是可以的。而另一种情况下

1
2
3
4
5
int& plus(int a, int b)
{
int c = a + b;
return c;
}

如果函数返回的是值而不是引用,程序会将返回值复制到临时变量区,从而返回这个值。而返回引用则会跳过这一环节。在上面的函数中,返回值 c 为局部变量,在函数调用结束后就会被释放,而返回 c 的引用相当于返回 c 的地址,之后程序再访问这个地址就会产生未定义的行为。

小结

虽然写了是萌新码代码的注意事项,看起来是给萌新看的,不过其实我应该也还算是萌新吧 ( ̄▽ ̄)" (虽然学编程的时间也不短了……)

这篇总结大部分都是来自侯捷老师的C++面向对象高级开发,最近看这个课件真的是受益匪浅,也很喜欢侯捷老师讲课的风格,推荐大家去看~