C编程学习之(二)——头文件的一些约定俗成的规则

https://www.cnblogs.com/Miranda-lym/p/5187606.html
https://www.cnblogs.com/xuepei/p/4027946.html
https://blog.csdn.net/qq_35779286/article/details/94169434
https://zhuanlan.zhihu.com/p/79480033
https://www.cnblogs.com/xiangtingshen/p/10980055.html

c/c++头文件中#ifndef/#define/#endif的用法

为什么要用#ifndef
  • 原因很简单:防止该头文件被重复引用:如
    • 指一个头文件在同一个c文件中被include了多次,这种错误常常是由于include嵌套造成的。如:存在a.h文件#include "c.h"而此时b.c文件导入了#include “a.h” 和#include "c.h"此时就会造成c.h重复包含。
  • 重复引用后果:
    • 有些头文件重复引用,只是增加了编译工作的工作量,不会引起太大的问题,仅仅是编译效率低一些
    • 有的文件重复引用,可能造成编译错误,如在头文件中定义了全局变量或写了函数的实现而不是声明(虽然这种方式不被推荐,但确实是C规范允许的),这种会引起重复定义
  • 在写头文件时 ,最好是把内容都写在#ifndef和#endif之间,用#ifndef/#define/#endif或者其他方式避免头文件重复包含,是一个好习惯。
如何使用#ifndef
#ifndef __XXX_H__    //意思是  "if not define __XXX_H__" 也就是没包含XXX.h
     
#define __XXX_H__   //就定义__XXX_H__

...  //此处放头文件中本来应该写的代码

#endif       //否则不需要定义 
  • #ifndef <标识> 标识一般写成头文件的名字的全大写,同时将点改为下划线,并在前后加上下划线,例如我们的“Complex.h”头文件就写成_Complex_H_
  • 若未定义XXX.h则这里就定义XXX.h,然后运行里面的内容,若下次还走到了这个文件,则进行#ifndef的判断,则#ifndef与#endif之间的内容不会再次被载入
  • #define 后可写一些类的定义,例如:
#ifndef _Complex_H_
#define _Complex_H_

class complex
{
private:
	//私有变量实部与虚部
	double real, imag;
public:
	complex();//空的构造函数
	complex(double, double);//默认参数的构造函数
	void set(double, double);//设置函数
	double getReal();//获取实部函数
	double getImag();//获取虚部函数
	//复数加减乘除函数
	complex add(complex);
	//复数显示函数
	void show();
};

#endif 

定义完头文件,我们要新建一个源文件“Complex.cpp”来实现头文件里的每一个方法。源文件的名字与头文件的名字保持一致,并且在源文件中必须include头文件,如下:

#include<stdio.h>
#include<cmath>
#include "Complex.h"

complex::complex()
{
	real = 0;
	imag = 0;
}

complex::complex(double a, double b)
{
	real = a;
	imag = b;
}

void complex::set(double a, double b)
{
	real = a;
	imag = b;
}

double complex::getReal()
{
	return real;
}

double complex::getImag()
{
	return imag;
}

complex complex::add(complex a)
{
	double real = this->real + a.getReal();
	double imag = this->imag + a.getImag();
	return complex(real, imag);
}



void complex::show()
{
	if (imag >= 0)
	{
		printf("%.2f+%.2fi", real, imag);
	}
	else
	{
		printf("%.2f%.2fi", real, imag);
	}
	return;
}

定义完头文件以及源文件,我们就可以新建一个测试文件“main.cpp”来测试我们的头文件类是否成功。
在该文件中引入头文件“Complex.h”就可以调用其中的complex类了

#include "Complex.h"
#include<stdio.h>

int main()
{
	complex A;//验证默认构造函数
	printf("A为:"); A.show(); printf("\n");
	complex B(2,-1);//验证两个参数构造函数
	printf("B为:"); B.show(); printf("\n");
	A.set(3,5);//验证设置函数;
	printf("A为:"); A.show(); printf("\n");
	//验证加减乘除
	complex C;
	C = A.add(B);
	printf("A+B为:"); C.show(); printf("\n");
	return 0;
}

此时的工程目录为
在这里插入图片描述

关于extern “c”

在用C++的项目源码中,经常会不可避免的会看到下面的代码:

#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
关于#ifdef _cplusplus/#endif _cplusplus
  • 上面介绍完,很明显#ifdef/#endif、#ifndef/#endif用于条件编译,#ifdef _cplusplus/#endif _cplusplus——表示如果定义了宏_cplusplus,就执行#ifdef/#endif之间的语句,否则就不执行。
  • C语言中不支持extern “C”声明,在.c文件中包含了extern “C”时会出现编译时错误。
extern关键字
  • extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
  • 通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
extern “c”
  • 我们知道extern “C”的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern “C”,表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。这样在类C的代码中就可以调用C++的函数or变量等。
  • 看一个例子:有moduleA、moduleB两个模块,B调用A中的代码,其中A是用C语言实现的,而B是利用C++实现的,下面给出一种实现方法:
//moduleA头文件
#ifndef __MODULE_A_H //对于模块A来说,这个宏是为了防止头文件的重复引用
#define __MODULE_A_H
int fun(int, int);
#endif
 
//moduleA实现文件moduleA.C //模块A的实现部分并没有改变
#include"moduleA"
int fun(int a, int b)
{
	return a+b;
}
 
//moduleB头文件
#idndef __MODULE_B_H //很明显这一部分也是为了防止重复引用
#define __MODULE_B_H
#ifdef __cplusplus //而这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件, 
extern "C"{ //因为cpp文件默认定义了该宏),则采用C语言方式进行编译
#include"moduleA.h"
#endif//其他代码
 
#ifdef __cplusplus
}
#endif
#endif
 
//moduleB实现文件 moduleB.cpp //B模块的实现也没有改变,只是头文件的设计变化了
#include"moduleB.h"
int main()
{
  cout<<fun(2,3)<<endl;
}

关于declspec

什么是
  • __declspec(dllexport)声明一个导出函数,是说这个函数要从本DLL导出。我要给别人用
  • __declspec(dllimport)声明一个导入函数,是说这个函数是从别的DLL导入。我要用。一般用于使用某个dll的exe中 不使用 __declspec(dllimport) 也能正确编译代码,但使用 __declspec(dllimport) 使编译器可以生成更好的代码。编译器之所以能够生成更好的代码,是因为它可以确定函数是否存在于 DLL 中,这使得编译器可以生成跳过间接寻址级别的代码,而这些代码通常会出现在跨 DLL 边界的函数调用中。但是,必须使用 __declspec(dllimport) 才能导入 DLL 中使用的变量。
extern “c” _declspec 与 _declspec
  • 因为C语言的编译器和C++的不同,C++的会有个叫做函数名打散的机制,比如函数:int add(int a,int b)在C++里面编译之后,就不是int add(int a,int b),而是类似:int add@WEROIUERH(int a,int b)的函数名,而且是随机的,如果不用extern "C ",C语言根本没法调用
  • 如果加了extern "C "则是用c的方式编译,函数在目标码中的内部修饰符就是_函数名,只要用函数名就可以调用
  • 又由于C语言没有函数重载,所以用EXTERN "C"的意思就是告诉编译器不要按照C++那样修改函数
    名称。
    https://q.cnblogs.com/q/114920/
  • 如果是c++,在编译器预处理的时候加上extern,如果是c语言调用的时候是不处理的,在写c加上没啥问题
在Linux中

_declspec是Microsoft VC中专用的关键字,所以不需要__declspec(dllimport) 也不需要__declspec(dllexport)

关于使用.def

可研究下如何使用
https://www.jianshu.com/p/ed7aee1060ab
http://www.voidcn.com/article/p-hbdnrxcc-ox.html

关于源文件包含头文件问题

简单的说时因为我们永远不要把外部函数的原型放到.c 文件中,因为如果这样,编译器无法检查函数声明定义的一致性,可能会导致运行时报错且不易察觉,而为了解决这个问题,将源文件包含自身的头文件,而自身头文件即包含了本源文件对函数的声明,所以编译器可以检查一致性;且,别的源文件再调用该函数,只需引用头文件即可也不会出错了。
具体可见https://blog.csdn.net/khwkhwkhw/article/details/49798985?utm_source=distribute.pc_relevant.none-task

关于一个头文件模板

  • 在GCC中
#idndef __MODULE_B_H //很明显这一部分也是为了防止重复引用
#define __MODULE_B_H
#ifdef __cplusplus //而这一部分就是告诉编译器,如果定义了__cplusplus(即如果是cpp文件, 
extern "C"{ //因为cpp文件默认定义了该宏),则采用C语言方式进行编译
#include"moduleA.h"
#endif//其他代码
 
#ifdef __cplusplus
}
#endif
#endif

具体看上面举例内容即可

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值