前言:
因为马上要去实习了,自感C++
和Data Structure
实在薄弱,怕去了给学校丢人囧,于是偶然找到了邓俊辉老师的das课程,以及这门课的讲义,看了一些后觉得甚是不错。
想通过观看课程视频和讲义及复现并比较代码提升两点:
1.respect coding conventions
,通过看大佬的代码,掌握必要的编程规范,比如C++11特性
、注释规范
等
2.对于之前遇到很多次,却从来没有真正搞明白的知识盲区,例如VS解决方案
、宏定义
等,做一次彻底的清扫
实际上,本博文是重点在于对于邓老师代码的剖析,把握编程规范,注意小细节,而非基础理论!
项目架构
这部分等我看完这篇博文再写
向量
vector.h
1.代码头规范
1.大段注释使用/**/
2.每个小点使用* xxxx
3.如果多项内容位于一行中,使用aaaa, bbbb
,例如Goudan Li, li@outlook.com
4.多名词并列时,使用aaa & bbb & ccc
,例如Computer Science & Technology
5.代码头包括:Copyright, Author (name, email, unit), Data, Description
等项目
贴上邓老师的代码头
/******************************************************************************************
* Data Structures in C++
* ISBN: 7-302-33064-6 & 7-302-33065-3 & 7-302-29652-2 & 7-302-26883-3
* Junhui DENG, deng@tsinghua.edu.cn
* Computer Science & Technology, Tsinghua University
* Copyright (c) 2003-2020. All rights reserved.
******************************************************************************************/
2.#pragma once
1.使用VS添加新的头文件时,会自动添加这行代码,值得注意的是后面并没有;
。
#pragma once
网上有的资料说该指令是宏定义,有的说是C++杂注,但无论如何其作用是保证对于一个编译单位(.cpp
)来说,该头文件只被编译一次,具体的编译过程可见这篇博文。
(此处补一下,
预处理
进行宏替换,删除注释和处理预处理指令(依赖的.h文件会展开),生成.i
文件
编译
进行语法检查和内联函数替换,生成汇编文件.s
汇编
将编译生成的汇编代码变为机器码,生成.o
文件
因为某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等),在程序中可能调用了某个库文件中的函数等。链接
将多个目标文件(编译单位及依赖的头文件共同生成的.o
文件)和所需的库文件(系统和第三方静态链接库.lib
,第三方动态库.dll、.so
),链接成.out
文件
)
#pragma once
有的编译器支持,有的编译器不支持,具体情况可查看编译器API文档,不过现在大部分编译器都有这个杂注了,其优点是是避免名字冲突。
2.另一种方式如下:
#ifndef SOMEFILE_H //如果没有定义SOMEFILE_H
#define SOMEFILE_H//定义SOMEFILE_H
//主体
#endif
#ifndef
检测指定的宏名字是否定义,如果未定义,则执行跟在其后的所有指令,直到出现#endif
。其优点是在所有支持C++语言的编译器上都是有效的,可移植性强。
3.使用using
代替typedef
using Rank = int; //秩 C++11 Standard
C++11鼓励使用别名using
,而非
typedef int Rank;
4.使用const
代替#define
虽然邓老师使用的是传统操作,注意#define
作为宏定义,后面不加;
#define DEFAULT_CAPACITY 3
但是C++11更鼓励使用具有C++特性的操作
const int kDefaultCapacity=3;
两者可以实现相同的功能,即使用DEFAULT_CAPACITY
来代替3
,
区别在于,参考这篇博文:
1.#define宏定义则是简单的替换,带来想不到的后果。
#define N 2
#define M 3+N
cout << M * 2;
简单替换输出3+2*2=7
。
const int N = 2;
const int M = 3 + N;
cout << M * 2;
输出正确结果10
2.当定义局部变量时,const所定义的变量的作用域,仅限于const定义所在的函数段;而使用宏定义#define,其作用域不仅限于定义所在的函数段,而是从定义点到整个程序的结束。
3.可以看到命名格式不同了,此处埋下伏笔,且看下面讲解。
5.命名规范
可以看得出,邓老师的C++命名遵从了Google的C++命名规范
,这篇博文讲的很好,本来打算有空自己翻译英文的,看来有人做了。
1.文件名小写,可以使用下划线或连字符进行分割,(邓老师最后使用大写字母区分版本或者平台,我觉得可吧)
vector.h
vector_insert.h
vector_search_binary_A.h
vector_search_binary_B.h
vector_partition_LGU.h
vector_partition_DUP.h
2.类型名首字母大写,不含下划线,适用于类class
、结构体struct
、类型定义typedef
、别名using
class Vector{};
class MyPalyerManager{};
struct MyTimer{};
using ArrayRank = int;
3.变量 (包括函数参数) 和数据成员名一律小写, 单词之间用下划线连接或者直接连接。类的成员变量以下划线结尾, 但结构体的就不用。(但实际上,我更喜欢使用下划线开头,方便查找)
class Vector{
private:
int _capacity; //容量
int _size; //规模
int* _elm_zone; //数据区 或者是 _elmzone
};
int math_score; // 好 - 用下划线.
int math_score; // 好 - 全小写.
int mathScore; // 差 - 混合大小写
4.静态存储类型变量(静态变量或全局变量)名以小写k开头,大小写混合。
const int kDefaultCapacity=3; //上面提到的
5.函数命名应该是首字母大写的大小写混合驼峰式命名。(邓老师采取的是首字母小写的小驼峰式命名,其实我更喜欢这一种)
void mergeSort ( Rank lo, Rank hi ); //归并排序算法
void heapSort ( Rank lo, Rank hi ); //堆排序(稍后结合完全堆讲解)
int partition ( Rank lo, Rank hi ); //轴点构造算法
void quickSort ( Rank lo, Rank hi ); //快速排序算法
void shellSort ( Rank lo, Rank hi ); //希尔排序算法
6.宏命名采用全大写加下划线的形式
#ifndef VECTOR_H
#define VECTOR_H
#endif
#define DEFAULT_CAPACITY 3
6.注释规范
1.代码空一格后添加注释
2.对于类来说,注释可以分为构造函数、析构函数、只读访问接口、可写访问接口
等
3.声明时注释仅仅需要指出算法名称,定义时可指出输入输出(我也不确定了)
4.类声明后,需指出,例如向量模版类
template<typename T> class Vector{ //向量模板类
protected:
private:
public:
};
7.普通对象和函数
与 模板类
的声明与定义
普通对象和函数:
通常情况下,将类的声明放置在.h
文件中,将定义放置在.cpp
文件中,当然.cpp
文件需要先#include "xxx.h"
。
其原因是,参见知乎回答:
链接器可以帮助引用了a.h
的cpp
文件编译生成.o
文件后,找到未定义的符号在a.o
中的实现,链接成可执行文件。
这样写的优势:
1.假设由a.h、a.cpp
和引用了a.h
的b.cpp、c.cpp、d.cpp
,如果将声明和定义都放在a.h
中,那么一旦更改定义,需要重新编译生成b.o、c.o、d.o
。如果分开定义,则只需要重新编译生成a.o
,链接器可以帮助b.o、c.o、d.o
在a.o
中找到实现体对应的二进制代码。
2.可以向使用者提供a.h
和定义生成的.lib
,从而隐藏具体的实现方式。
模板类
模板类的声明和实现都需要放在.h
文件中,声明和定义可以选择不放在同一个.h
文件中,但要在声明的那个头文件结尾处引用上定义的.h文件,例如如下结构是合理的。
vector.h中
#pragma once
template<typename T> Vector{ //向量模板类
protected:
int _capacity;int _size;T *_elem; //容量 规模 数据区
void copyFrom(T& A,int lo,int hi); //区间复制
void sort(); //排序
};
//必须包含如下两行
#include "vector_copyFrom.h"
#include "vector_sort.h"
vector_copyFrom.h中
template<typename T>
void Vector<T>::copyFrom(T&A,int lo,int hi){
_elem=new T[_capacity=2*(hi-lo)];
for(_size=0;_size<hi-lo;_elem[_size++]=A[lo+_size]);
};
vector_sort.h中
template<typename T>
void Vector<T>::sort(){
};
其原理是
《C++编程思想》: 模板定义很特殊。由template<…> 处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。