【C++核心编程】extern关键字&头文件重复

 🔥博客主页: 我要成为C++领域大神

🎥系列专栏【C++核心编程】 【计算机网络】 【Linux编程】 【操作系统】

❤️感谢大家点赞👍收藏⭐评论✍️

本博客致力于分享知识,欢迎大家共同学习和交流。

头文件中变量和函数的声明

修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”

1. extern修饰变量的声明

如果文件a.cpp需要引用b.cpp中变量int v,就可以在a.cpp中声明extern int v,然后就可以引用变量v。

这里需要注意的是,被引用的变量v的链接属性必须是外链接(external)的,也就是说a.cpp要引用到v,不只是取决于在a.cpp中声明extern int v,还取决于变量v本身是能够被引用到的。

这涉及到c语言的另外一个话题--变量的作用域。能够被其他模块以extern修饰符引用到的变量通常是全局变量

还有很重要的一点是,extern int v可以放在a.cpp中的任何地方,比如你可以在a.cpp中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都心存顾虑。好像extern声明只能用于文件作用域似的。

#include <stdio.h>
 
// 函数外定义变量 x 和 y
int x;
int y;
int addtwonum()
{
    // 函数内声明变量 x 和 y 为外部变量
    extern int x;
    extern int y;
    // 给外部变量(全局变量)x 和 y 赋值
    x = 1;
    y = 2;
    return x+y;
}
 
int main()
{
    int result;
    // 调用函数 addtwonum
    result = addtwonum();
    
    printf("result 为: %d",result);
    return 0;
}

其中,变量在头部就已经被声明,但是定义与初始化在主函数内

输出的结果是result为:3

2. extern修饰函数声明。

从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。
如果文件a.cpp需要引用b.cpp中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。
就像变量的声明一样,extern int fun(int mu)可以放在a.cpp中任何地方,而不一定非要放在a.cpp的文件作用域的范围中。

#include <stdio.h>
/*外部变量声明*/
extern int x;
extern int y;
int addtwonum()
{
    return x + y;
}
#include <stdio.h>
  
/*定义两个全局变量*/
int x=1;
int y=2;
extern int addtwonum();
int main(void)
{
    int result;
    result = addtwonum();
    printf("result 为: %d\n",result);
    return 0;
}

对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。使用extern和包含头文件来引用函数有什么区别呢?extern的引用方式比包含头文件要简洁得多!extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。

这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型程序编译过程中,这种差异是非常明显的。

在头文件中声明类

在头文件中将类的属性和类成员函数,以及构造·析构函数 声明出来,在对应的源文件中 进行类成员函数的定义,以及通过定义构造函数 来初始化类成员属性。

在头文件A.h中声明类:

#pragma once
void fun();
class CTest{
public:
int m_a;
static int m_b;
const int m_c;
CTest();
~CTest();
static void funStatic();
void funConst() const;
virtual void funVirtual();
};

在对应的源文件A.cpp中定义类:其中,我们需要注意的是,类中三种不同的函数在定义时,可能需要删除关键字。

static void funStatic(); 静态成员函数 定义时,去掉static关键字
void funConst() const; 常量成员函数,在定义时,保留const关键字
virtual void funVirtual(); 虚函数,在定义时,删除virtual关键字

#include<iostream>
#include"A.h"
int a = 1;
int b = 2;
void fun() {
    std::cout << "fun" <<std::endl;

}
//静态成员属性定义:
int CTest::m_b = 3;
//构造函数的定义
CTest::CTest() :m_a(10) ,m_c(3){
    std::cout<<"CTest" << std::endl;
}
//析构函数的定义
CTest::~CTest() {
    std::cout << "~CTest" << std::endl;
}
//静态函数在定义时,去掉static关键字
void CTest::funStatic() {
    std::cout << "funStatic" << std::endl;
}
//常函数在定义时,保留const关键字
void CTest::funConst() const {
    std::cout << "funConst" << std::endl;
}
//虚函数在定义时,去掉virtual关键字
void CTest::funVirtual() {
    std::cout << "funVirtual" << std::endl;
}

头文件和源文件的区别

区别:

1.头文件:变量的声明,函数的声明,类的定义、类成员的声明 源文件:变量的定义,函数的定义,类中成员属性的定义,成员函数的定义

2.单独的头文件不参与编译,源文件 自上而下进行编译当我们编写一个含有语法错误的头文件后,编译正常通过。因此我们可以得出,单独的头文件不参与编译。

但是当源文件中包含此头文件时:编译无法通过

头文件重复

在vs中,当我们添加头文件时,工具会为我们自动在代码上方添加一段"#pragma once"的代码,意为 仅执行一次 。如果在源文件中,仅仅引入了一次头文件,则可以不进行去重操作。若多次引入,则编译器会提示错误:重定义

头文件:

//A.h
//#pragma once
class A
{
public:
int m_a;
A();
};
//B.h
//#pragma once
#include"A.h"
class B {
public:
A a1;
};
//C.h
//#pragma once
#include"A.h"
class C {
public:
A a2;
};

源文件:

#include "A.h"
A::A() :m_a(1) {};
#include<iostream>
#include"B.h"
#include"C.h"
using namespace std;
int main() {
    B b;
    C c;
    cout << b.a1.m_a << endl;
    cout << c.a2.m_a << endl;
    return 0;
}

此时我们去编译,无法通过,编译器提示我们重定义。这里我们需要了解编译器执行的原理。我们引入头文件,本质是对代码的展开替换,将对应头文件的代码替换过来。若未进行去重处理,当引入的头文件中含有重复定义的变量时,则会出现重定义。

引入头文件的本质:

那么,我们应该如何避免重定义呢?

1.使用vs工具给我们添加的#pragma once 写法简单,效率略高

2.基于宏的逻辑判断 效率低,宏名字有可能会重复,导致程序逻辑错误

    #ifndef 宏名字

    #define 宏名字 

    ....代码....

    #enddef //宏名字

意为,若在#enddef后面的代码中重复定义相同的宏,则不会执行后面代码中#ifndef #enddef里的内容

#ifndef __A_h__
#define __A_h__
class A
{
public:
int m_a;
A();
};
#endif

编译可以正常通过。

重复部分的代码变成了灰色,被忽略了。

告诉编译器当前的这个头文件在其他的源文件中只会包含一次

#pragma once :写法简单,效率略高

  • 29
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值