C++封装常用对象和对头文件以及预编译机制的探索
在C++实际开发中,难免会使用到一些你极为常用的算法(比如笔者经常使用的多线程技术),实现这些算法的类或是全局函数或是命名空间等等经常都要被使用多次,你会有哪些办法来使用呢?笔者有4个办法。
第一个方法就是你直接重新编写一个和原来一样的算法,但是这种方法又费时又费力,效率不高,只有初学者在没有办法的时候才会使用这种方法。第二种方法也是如此,就是复制一份代码到新写的文件中,这种方法的缺点是你不确定能否找到之前的代码,而且一点也不像IT人员的解决方案。
我们重点介绍第三种和第四种方法。
第三种方法是在需要的地方声明这个类或是全局函数。注意声明的时候要使用extern关键字来声明(任何东西都是如此:全局函数、类、结构……)比如下面的代码:
//a.cpp
#include <stdio.h>
void print(){
pringf("HelloWorld");
} //b.cpp
extern pingt();
int main(){
print();
return 0;
}
这个代码在b.cpp中声明了一个a.cpp文件中的函数并且使用了存储类声明符——extern。在这里我先为大家介绍一下C++的存储类。存储类说明的是一个对象的存储方法,通常有以下4种
1、extern 只声明不定义,比如声明一个变量却不分配内存,一般用于两个文件中的共享(a文件中定义了一个类,想要在b文件中访问,则必须在b文件首部声明一个extern的a文件中的类,上面示例使用的就是这种方法);
2、static 静态,表示把这个东西定义在堆而不是栈里。全局的对象一般都是静态的,但是如果显示声明,则表示我发在其他文件中使用extern来调用它。如果在一个本应该定义在栈的代码块中声明成static,也将声明在堆中(比如全局函数);
3、register 注册,把一个对象声明到寄存器中,但是如果使用&操作符在程序中显示地取地址,编译器也将把这个对象定义到内存而不是寄存器中。使用register一般可以加快处理速度和减少内存使用量。
4、auto 默认、自动,声明一个对象的默认形式,和static正好相反,不能把一个全局对象声明成auto。
那么,我们就明白了刚才那段代码中我们使用extern是为什么了。这种方法很常用,但是并不是最好的方法。
接下来,隆重介绍我们的第四种方法——头文件!什么?你说你没有听说过头文件(head)?不可能,只要你写的不是既不输出也不输入的console程序,也不是窗口中什么也没有的程序,那么你就必须用到头文件!看看你的C++编译器中include目录吧,那就是C++语言的标准头文件库,里边有C编写控制台程序时常用的stdio还有C++新标准的iostream等等。没错,你猜对了,我们第四种方法就是编写头文件。哦哦哦,别把这个方法看的太难!其实就是把第三种方法变了变而已……但是的确方便得多。而且有一些封装之类的思想。
好的,首先我们先来重新了解一下头文件,如果你是高手,请让你的思绪回到最开始编写HelloWorld的时候。那么,我首先要纠正一个问题——头文件的定义。头文件的定义不是“C++提供给程序员的类库,是一些API之类的东西……”云云,而是“将一些类、全局函数、宏等资源集中声明,并在在include后代替声明,类似于类库或叫做封装。”当然,这不是官方的定义,是我根据个人理解而得出的。还有一个误区——文件扩展名,很多windows新手都认为文件扩展名很重要,这其实是windows给我们的一个错觉。因为windows总是以扩展名定义文件并说明文件使用哪种命令打开,但如果你使用过其他的操作系统就知道了,C++源文件并不需要必须是cpp,当然,头文件也不需要都是h。都是文件,里边都存储的是二进制代码,编译器也能编译。比如C++标准的头文件ios啦、cmath等等都没有扩展名。
好了,所有需要的误区也都说完了,我们来说第四种方法。首先看一个例程:
//a.cpp
#include <iostream>
using namespace std;
void print(){
cout<<"HelloWorld";
} //main.cpp
int main(){
print();
}
你有什么办法让编译器不报错呢?现在我们来编写一个头文件。
print();
没错,就一行,这就是一个头文件,我们把它保存成c.h。然后把上面的a.cpp和main.cpp改一下。
//a.cpp
#include <iostream>
#include "c.h" //注意:要把这三个文件放在同一目录 using std::cout; void print(){
cout<<"HelloWorld";
} //main.cpp
#include "c.h" int main(){
print();
return 0;
}
OK!你有没有发现你成功了呢?而且,你可以编写头文件了!(如果你失败了,联系我的qq:2276768747,验证全写本文连接地址)
接下来,我来先讲讲include这个预编译指令。什么?#include吗?我早学过了。是的,我先说的东西80%的人都知道,但是接着,我要说的80%的人不知道:#include我们一般使用两种语法——1、#include <{编译器系统include目录下的文件}>;2、#include “{本目录下或是编译器include目录的头文件}” 注意到他们的不同了吗?他们的不同是<>和””还有,第一种只在include目录下寻找头文件,而第二种在当前目录和include目录下寻找头文件。这个功能使得引入自行编写的头文件成为可能(没有也可以,可以放到include目录下),所以我们可以将声明甚至是定义放到头文件里面。还有一种情况,就是把声明和定义全部放到头文件里面,这样可以避免在使用模板和泛型的时候出现问题,请看下面的代码:
//c.h
//把声明和定义全部放在头文件里,可以使用模板template
#include <iostream> using namespace std; void print(){
cout<<"HelloWorld";
} //main.cpp
#include "c.h" int main(){
print();
return 0;
}
还有一些我要说:我们可以继续模仿C++开发者——让我们编写的头文件更“正式”(当然,不是给你讲那成山的注释是怎么回事,更不是强逼迫你那么做,我要讲述一些头文件中经常使用的预编译指令)。首先我要给大家一个详细的表格:
#空指令,无任何效果
#include包含一个源代码文件
#define定义宏
#undef取消已定义的宏
#if如果给定条件为真,则编译下面代码
#ifdef如果宏已经定义,则编译下面代码
#ifndef如果宏没有定义,则编译下面代码
#elif如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif结束一个#if……#else条件编译块
#error停止编译并显示错误信息
接下来,我一点点的解释上面的宏定义指令,他可以让你的头文件具有判断功能并且使你的头文件变得更加正规和灵活。
首先,我们的老朋友#include,我们已经深入了解过了include的所有“底细”,在此不再赘述。
然后我们来看看#define。#define在C语言编程中很常用,一般用于定义一些具有特殊意义的全局函数或者常量,其正式名称是“宏定义语句”,我们并不多讲,只举一个例子:
#define PI 3.14 //定义π的值,常量
接着看#undef,它是#define的逆运算,即消除宏定义:
#define PI 3.14
//PI可用
#undef PI
//PI不再可用
#if是条件判断语句,这个是判断条件是否为真,为真执行#if到#endif之内的语句然后执行下方语句,否则直接执行下方语句,这个在头文件中传输信息时用的比较多,重点讲一下:
#define DEBUG 0 //定义DEBUG,为0,在C++中,0代表假,1代表真 main()
{
#if DEBUG //所以显然这里的条件是假,那么就不会输出Debugging(当然我不想老调试)
printf("Debugging ");
#endif //之后的语句将执行,也就是说程序执行后会输出Running
printf("Running ");
} //如果把define改成#define DEBUG 1,将会输出:
//Debugging
//Running
我最开始也不能理解,但是用得多了,就能够很好地理解这个#if,其实它和我们C++的if语句一样,下面会讲到else……(实质上我也不知道会在哪里用到,只是写封装的时候避免一些重复引用的问题云云)
接着说一下ifdef和ifndef,这里注意:#ifdefined等价于#ifdef;#if!defined等价于#ifndef。也就是说ifdef的意思是判断是否定义,如果定义了,就执行下面语句,否则不执行(else除外)而ifndef正好相反。话不多少看示例:
#define DEBUG //注意,这里定义了DEBUG main()
{
#ifdef DEBUG //很显然,这是真,将会输出yes
printf("yes ");
#endif
#ifnde fDEBUG //DEBUG没定义吗?当然定义了,所以no不会输出
printf("no ");
#endif
}
//输出结果:yes
//如果注释掉第一句,将会输出no
好了,我们终于可以讲else了(我不希望你因为不耐烦和自以为掌握了封装而不看下去,否则你将吃大亏的!你的头文件总会出现重名、重复引用之类的东西,由于头文件没有main主函数,只能通过预处理来进行判断条件等操作)。看看else,前面我就说了,预处理和C++语法一样,所以这代表“否则”的意思。一行代码+注释远远大于100行解释+说明(我的名言,记住了,先记住了,然后仔细看示例):
#define DEBUG //定义了DEBUG main()
{
#ifndef DEBUG //刚才说过的,不多解释,将不会会输出NoDebugging
printf("Nodebugging ");
#else //否则,也就是说定义了DEBUG的时候,将会输出Debugging
printf("Debugging ");
#endif
printf("Running "); //在endif之后,将会输出RUnning
}
//输出结果:
//Debugging
//Running
这个也很简单吧!接下来,我们回忆一下我们用过的if……else if……这种语句格式。在判断性预编译机制中,我们也有这种类似的语句——#elif。不多说了,我们来看看示例代码:
#define TWO //定义一个TWO,记住了! main()
{
#ifdef ONE //这里是判断是否定义ONE,所以不会输出1
printf("1 ");
#elifdefined TWO //我不知道这里是否能够缩写成elifdef,但是,的意思是else if defined TWO,也就是会输出2
printf("2 ");
#else //否则,这里不会执行,如果不懂的话就想想else if语句块
printf("3 ");
#endif //预编译判断结束
}
接着是这个:
#error指令将使编译器显示一条错误信息,然后停止编译。
#line指令可以改变编译器用来指出警告和错误信息的文件号和行号。
#pragma指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。
这个很简单了,我就不再多说了,看网上的定义就可以了,这个error还挺常用的,仔细看一下,有兴趣的网上搜搜也无妨。接着我浅谈一下C++预处理机制对我们的封装的作用:
1、由于我们封装的头文件没有main方法,不能对于程序的执行的流程和引用文件等操作进行流程控制;
2、由于C++中头文件会被自动inline,所以需要预处理一些引用以便避免重名和重复引用;
3、我们编写头文件封装的时候难免会使用命名空间和类、结构之类的东西,所以我们一般需要控制生成和调用流程以便保证使用封装时的安全。这里多说一下:在if系列中的预处理语句中,除了可以放输出,还可以放define甚至需要限定某些条件下会执行的语句,所以说,#if系列有的时候也被叫做“头文件的if语句”。
希望大家掌握上述知识,灵活运用,以便使得将来的项目(过去的就算了,先别改了)更加稳定、灵活、高效。
在此特别声明:本文中有关于预处理的例程和定义均来自http://www.kuqin.com/language/20090806/66164.html,但是为其添加了注释并修改了格式,笔者为时间仓促没有另行通知而梳表歉意,如原作者不希望笔者使用,请与我联系:zhangyutong@zhangyutong.net。感谢各位,文章最后我将留下我的联系方式。
QQ:2276768747 MSN:xztzrjcxxzz@hotmail.com email:zhangyutong@zhangyutong.net 感谢阅读金鸡独立提供的计算机技术文章!再见!
C++封装常用对象和对头文件以及预编译机制的探索的更多相关文章
- C++封装常用对象和对头文件探索
在C++实际开发中,难免会使用到一些你极为常用的算法(比如笔者经常使用的多线程技术),实现这些算法的类或是全局函数或是命名空间等等经常都要被使用多次,你会有哪些办法来使用呢?笔者有4个办法. 第一个方 ...
- C++头文件中预编译宏的目的
C++头文件中预编译宏的目的 eg: #ifndef _FACTORY_H_#define _FACTORY_H_......#endif //~_FACTORY_H_ 防止头文件被重复包含,导致变量 ...
- 删除moduleCache下文件解决预编译头文件相关的编译错误
之前有在代码全部正确的情况下,遇到过下面的编译错误: fatal error: file '.....h' has been modified since the precompiled header ...
- C预编译, 预处理, C/C++头文件, 编译控制,
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作.#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的 ...
- 通过预编译头文件来提高C++ Builder的编译速度
C++ Builder是最快的C++编译器之一,从编译速度来说也可以说是最快的win32C++编译器了.除了速度之外,C++builder的性能也在其它C++编译器的之上,但许多Delphi程序员仍受 ...
- C++预编译头文件 – stdafx.h
预编译头文件的由来 也许请教了别的高手之后,他们会告诉你,这是预编译头,必须包含.可是,这到底是为什么呢?预编译头有什么用呢? 咱们从头文件的编译原理讲起.其实头文件并不神秘,其在编译时的作用,就是把 ...
- VS中c++文件调用c 函数 ,fatal error C1853 预编译头文件来自编译器的早期版本号,或者预编译头为 C++ 而在 C 中使用它(或相反)
出现错误:error C1853: "Debug\ConsoleApplication1.pch"预编译头文件来自编译器的早期版本号.或者预编译头为 C++ 而在 C 中使用它(或 ...
- 预编译头文件pch
1. 预编译头文件 作用:提高编译效率.预编译头文件(扩展名为.PCH),是为了提高编译效率而使用的一种方法,把一个工程中较稳定的代码预先编译好放在一个文件(.PCH)里.避免每次编译 ...
- MyWebViewDemo【封装Webview常用配置和选择文件、打开相机、录音、打开本地相册的用法】
版权声明:本文为HaiyuKing原创文章,转载请注明出处! 前言 封装webview的常用配置和选择文件.打开相机.录音.打开本地相册的用法.[如果想要使用简单的预览功能,可以参考<MyBri ...
随机推荐
- 0c-42-ARC模式下如何兼容非ARC的类
1.ARC模式下如何兼容非ARC的类 让程序兼容ARC和非ARC部分.转变为非ARC -fno-objc-arc 2.将MRC转换为ARC ARC也需要考虑循环引用问题:一端用strong,一端用we ...
- linux下的十六进制编辑器---wxHexEdit
....其实wxHexEdit是一个跨平台的十六进制编辑器,支持windows,linux,mac. 之所以标题用linux...是因为windows下多数都用winhex,UE之类的编辑器,而lin ...
- Windows 之 win10快捷键
1.Windows10系统常用快捷键: 1)贴靠窗口(窗口可以变为1/4大小放置在屏幕4个角落):Win +左/右 或 Win +上/下 2)切换窗口:Alt + Tab 3)任务视图(松开键盘界面不 ...
- JSON特殊字符处理
JSON 是适用于 Ajax 应用程序的一种有效格式,原因是它使 JavaScript 对象和字符串值之间得以快速转换.由于 Ajax 应用程序非常适合将纯文本发送给服务器端程序并对应地接收纯文本,相 ...
- IOS webView快照
这个功能就是对网页的存储,存储成png格式的图片 且不失真 很棒的一个小方法.具体实现如下: - (void)webViewDidFinishLoad:(UIWebView *)webView1 { ...
- nodejs的mysql模块学习(五)数据库连接配置之SSL
SSL选项 在SSL连接选项中需要一个字符串 或者对象 当是字符串的时候 将使用预定义的SSL配置文件 "Amazon RDS" 只有这一个预定义配置文件 用来连接到亚马逊RDS服 ...
- Java中transient有何作用?
transient关键字用来防止序列化域.如果一个引用类型被transient修饰,则其反序列化的结果是null.基本类型则为0.如果引用类型时不可序列化的类,则也应该使用transient修饰,它在 ...
- web前端开发(4)
低权重原则,避免滥用子选择器 CSS选择符是有权重的,当不同选择符的样式设置有冲突时会采用权重高的选择符设置的样式. html标签权重是1,class权重是10,id权重是100 如果css选择符权重 ...
- PHP实现无级递归分类(ThinkPHP框架)
/** * 无级递归分类 * @param int $assortPid 要查询分类的父级id * @param mixed $tag 上下级分类之间的分隔符 * @return string $tr ...
- 正则转nfa:bug消除
正则到nfabug的解决方法 前面提到了这个bug,为了解决这个bug,我们必须在每次引用到一个假名的时候,都构建一个拷贝.现在假设我们遇到了一个假名,并得到了他的开始节点和结束节点,当前的难题就是构 ...