在Visual C++中进行类设计的通行做法(中)——类的定义

 

我是荔园微风,作为一名在IT界整整25年的老兵,今天我们来重新审视一下在Visual C++中进行类设计的通行做法,这一篇帖子来看看在搭建好基本架构后如何把程序调整好可运行。程序员新手会去看很多书,但是书中往往教语法多,讲程序员习惯的少。那我们就来谈谈这个话题。

上一篇帖子把程序建好写好后,接下来单击菜单【生成】→【生成study】,编译结果会报一堆错误!会有很多提示类重复定义的报错。

其实都是由Father类重复定义引起的,那么为什么会出现这个错误?请大家仔细查看study.cpp文件,在这个文件中包含了father.h和son.h这两个头文件。当编译器编译 study.cpp文件时,因为在文件中包含了father.h头文件,编译器展开这个头文件,知道Father这个类定义了,接着展开son.h头文件,而在son.h头文件中也包含了father.h,再次展开father.h,于是Father这个类就重复定义了。

要解决头文件重复包含的问题,可以使用条件预处理指令。所谓预处理指令,就是C/C++语法里说的#ifndef #define #endif,可能有同学已经把这个给忘记了。

#ifndef, #define, 和 #endif是C/C++的预处理指令,常常用来条件编译和防止头文件重复包含。#ifndef 它是if not define的简写,是宏定义的一种,确切的说是预处理功能(的一种——条件编译。

使用#ifndef可以避免以下错误:如果在.h文件中定义了全局变量,一个C/C++文件包含了.h文件多次,如果不加#ifndef宏定义,会出现变量重复定义的错误;如果加了#ifndef则不会出现这种错误。

#ifndef:用于判断指定的标识符是否已经被定义。如果该标识符未定义,则执行下面的代码块,否则跳过。

#define: 用于定义指定的标识符,通常用于定义宏。在条件编译中,一般用来定义一个标识符,以便在后面的代码中进行条件判断。

#endif :用于结束条件编译的代码块。

修改后的头文件如下:

father.h代码如下所示。

#ifndef FATHER_H_H
#define FATHER_H_H

class Father

{
public:
  Father();
  ~Father();
  void eat();
  void run();
  virtual void study();
};

#endif

我们一般用#define定义一个宏,是为了在程序中使用,能使程序更加简洁,维护更加方便,然而在此处,我们只是为了判断FATHER_H_H是否定义,以此来避免类重复定义,因此我们没有为其定义某个具体的值。在选择宏名时,要选用一些不常用的名字,因为我们的程序经常会跟别人写的程序集成,如果选用一个很常用的名字,则有可能会造成一些不必要的错误。

son.h代码如下所示。

#include "father.h"

#ifndef SON_H_H
#define SON_H_H

class Son:public Father
{
public:
  void study ();
};
#endif

我们再看study.cpp的编译过程。当编译器展开father.h头文件时,条件预处理指令判断FATHER_H_H没有定义,于是就先定义它,然后继续执行,定义了Father这个类;接着展开son.h头文件,而在son.h头文件中也包含了father.h,再次展开father.h时条件预处理指令发现FATHER_H_H已经定义,于是跳转到#endif,执行结束。

通过分析,我们发现在这次的编译过程中,Father这个类只定义了一次。再次编译执行程序,就能看到正确的结果了。

在前面一篇帖子中,我们提到,Visual Studio 2022生成的头文件中自动添加了一句指令代码:#pragma once,那么该代码有何作用呢?下面我们在father.h和son.h头文件中取消对该代码的注释,同时将我们编写的条件预处理指令代码注释起来。修改后的头文件如下:

father.h代码如下所示。

#pragma once

//#ifndef FATHER_H_H
//#define FATHER_H_H

class Father
{
public:
  Father();
  ~Father();
  void eat();
  void run();
  virtual void study ()
};

//#endif

son.h代码如下所示。

#pragma once
#include "father.h"

//#ifndef SON_H_H
//#define SON_H_H

class Son:public Father
{
public:
  void study ();
};

//#endif

再次编译执行程序,可以看到没有出现头文件重复包含的错误。实际上,#pragma once这句代码的也是用来避免头文件被重复包含的,不同的是,该指令代码是Visual Studio 2022的C++编译器独有的,在其他平台上编写C++代码不能用该指令。如果只是编写运行于Windows平台下的C++程序,那么可以用该指令来代替条件预处理指令的使用,简单且方便。


接下来,我们看看Visual Studio 2022每次生成项目给我们自动生成的pch.h文件有什么作用。打开pch.h文件,你可以看到如下代码:

pch.h代码如下所示。

#ifndef PCH_H
#define PCH_H

//TODO:添加要在此处预编译的标头

#endif //PCH_H

原来这只是一个预编译头文件(使用条件预处理指令),其作用也是为了防止头文件的重复包含,我们可以将常用的头文件在TODO的位置引入,或者将一些全局变量的定义放在这里。该文件没有任何实质内容,但是我们编写的源文件还必须要包含这个头文件,否则,编译就要出错,这是为什么呢?这是因为Visual Studio 2022在生成项目时,强制使用了该预编译头文件,所以你的源文件才不得不包含它。

要想不使用该文件也很简单,在Visual Studio 2022的解决方案资源管理器窗口中,右键单击项目名称,然后选择“属性”以打开项目“属性页”对话框。在“ 配置 ”下拉列表中,选择“ 所有配置”。选择 “配置属性>C/C++>预编译标头 ”属性页。在属性列表中,选择 预编译标头 属性的下拉列表,然后选择“ 不使用预编译标头”。 选择“确定”以保存更改 。单击“确定”按鈕后,我们就可以在源文件中刪除对pch.h头文件的引用了。

作者简介:荔园微风,1981年生,高级工程师,浙大工学硕士,软件工程项目主管,做过程序员、软件设计师、系统架构师,早期的Windows程序员,Visual Studio忠实用户,C/C++使用者,是一位在计算机界学习、拼搏、奋斗了25年的老将,经历了UNIX时代、桌面WIN32时代、Web应用时代、云计算时代、手机安卓时代、大数据时代、ICT时代、AI深度学习时代、智能机器时代,我不知道未来还会有什么时代,只记得这一路走来,充满着艰辛与收获,愿同大家一起走下去,充满希望的走下去。

你可能感兴趣的:(Visual,Studio技术,c++,开发语言,microsoft,windows,微软)