C++ 全局变量的声明和定义

转自:http://qiusuoge.com/8839.html

(1)编译单元(模块)
    在VC或VS上编写完代码,点击编译按钮准备生成exe文件时,编译器做了两步工作:
第一步,将每个.cpp(.c)和相应的.h文件编译成obj文件;
第二步,将工程中所有的obj文件进行LINK,生成最终.exe文件。

    那么,错误可能在两个地方产生:
一个,编译时的错误,这个主要是语法错误;
一个,链接时的错误,主要是重复定义变量等。
   
    编译单元指在编译阶段生成的每个obj文件。
    一个obj文件就是一个编译单元。
    一个.cpp(.c)和它相应的.h文件共同组成了一个编译单元。
    一个工程由很多编译单元组成,每个obj文件里包含了变量存储的相对地址等。

(2)声明与定义
函数或变量在声明时,并没有给它实际的物理内存空间,它有时候可保证你的程序编译通过;
    函数或变量在定义时,它就在内存中有了实际的物理空间。
 
    如果你在编译单元中引用的外部变量没有在整个工程中任何一个地方定义的话,那么即使它在编译时可以通过,在连接时也会报错,因为程序在内存中找不到这个变量。

    函数或变量可以声明多次,但定义只能有一次。

(3) extern作用
    作用一:当它与"C"一起连用时,如extern "C" void fun(int a, int b);,则编译器在编译fun这个函数名时按C的规则去翻译相应的函数名而不是C++的。
    作用二:当它不与"C"在一起修饰变量或函数时,如在头文件中,extern int g_nNum;,它的作用就是声明函数或变量的作用范围的关键字,其声明的函数和变量可以在本编译单元或其他编译单元中使用。

    即B编译单元要引用A编译单元中定义的全局变量或函数时,B编译单元只要包含A编译单元的头文件即可,在编译阶段,B编译单元虽然找不到该函数或变量,但它不会报错,它会在链接时从A编译单元生成的目标代码中找到此函数。

(4)全局变量(extern)
    有两个类都需要使用共同的变量,我们将这些变量定义为全局变量。比如,res.h和res.cpp分别来声明和定义全局变量,类ProducerThread和ConsumerThread来使用全局变量。(以下是QT工程代码)

[cpp] view plaincopy
/**********res.h声明全局变量************/ 
#pragma once 
 
#include <QSemaphore> 
 
const int g_nDataSize = 1000; // 生产者生产的总数据量 
const int g_nBufferSize = 500; // 环形缓冲区的大小 
 
extern char g_szBuffer[]; // 环形缓冲区 
extern QSemaphore g_qsemFreeBytes; // 控制环形缓冲区的空闲区(指生产者还没填充数据的区域,或者消费者已经读取过的区域) 
extern QSemaphore g_qsemUsedBytes; // 控制环形缓冲区中的使用区(指生产者已填充数据,但消费者没有读取的区域) 
/**************************/ 

上述代码中g_nDataSize、g_nBufferSize为全局常量,其他为全局变量。

[cpp] view plaincopy
/**********res.cpp定义全局变量************/ 
#pragma once 
#include "res.h" 
 
// 定义全局变量 
char g_szBuffer[g_nBufferSize]; 
QSemaphore g_qsemFreeBytes(g_nBufferSize); 
QSemaphore g_qsemUsedBytes; 
/**************************/ 

在其他编译单元中使用全局变量时只要包含其所在头文件即可。

[cpp] view plaincopy
/**********类ConsumerThread使用全局变量************/ 
#include "consumerthread.h" 
#include "res.h" 
#include <QDebug> 
 
ConsumerThread::ConsumerThread(QObject* parent) 
    : QThread(parent) { 
 

 
ConsumerThread::ConsumerThread() { 
 

 
ConsumerThread::~ConsumerThread() { 
 

 
void ConsumerThread::run() { 
     for (int i = 0; i < g_nDataSize; i++) { 
          g_qsemUsedBytes.acquire();               
          qDebug()<<"Consumer "<<g_szBuffer[i % g_nBufferSize]; 
          g_szBuffer[i % g_nBufferSize] = ‘ ‘; 
          g_qsemFreeBytes.release(); 
          
     } 
     qDebug()<<"&&Consumer Over"; 

/**************************/ 

    也可以把全局变量的声明和定义放在一起,这样可以防止忘记了定义,如上面的extern char g_szBuffer[g_nBufferSize]; 然后把引用它的文件中的#include "res.h"换成extern char g_szBuffer[];。
    但是这样做很不好,因为你无法使用#include "res.h"(使用它,若达到两次及以上,就出现重定义错误;注:即使在res.h中加#pragma once,或#ifndef也会出现重复定义,因为每个编译单元是单独的,都会对它各自进行定义),那么res.h声明的其他函数或变量,你也就无法使用了,除非也都用extern修饰,这样太麻烦,所以还是推荐使用.h中声明,.cpp中定义的做法。

(5)静态全局变量(static)
    注意使用static修饰变量,就不能使用extern来修饰,即static和extern不可同时出现。
    static修饰的全局变量的声明与定义同时进行,即当你在头文件中使用static声明了全局变量,同时它也被定义了。
    static修饰的全局变量的作用域只能是本身的编译单元。在其他编译单元使用它时,只是简单的把其值复制给了其他编译单元,其他编译单元会另外开个内存保存它,在其他编译单元对它的修改并不影响本身在定义时的值。即在其他编译单元A使用它时,它所在的物理地址,和其他编译单元B使用它时,它所在的物理地址不一样,A和B对它所做的修改都不能传递给对方。
    多个地方引用静态全局变量所在的头文件,不会出现重定义错误,因为在每个编译单元都对它开辟了额外的空间进行存储。

以下是Windows控制台应用程序代码示例:

[cpp] view plaincopy
/***********res.h**********/ 
static char g_szBuffer[6] = "12345"; 
void fun(); 
/************************/ 

[cpp] view plaincopy
/***********res.cpp**********/ 
#include "res.h" 
#include <iostream> 
using namespace std; 
 
void fun() { 
     for (int i = 0; i < 6; i++) { 
          g_szBuffer[i] = ‘A’ + i; 
     } 
     cout<<g_szBuffer<<endl; 

/************************/ 

[cpp] view plaincopy
/***********test1.h**********/ 
void fun1(); 
/************************/ 

[cpp] view plaincopy
/***********test1.cpp**********/ 
#include "test1.h" 
#include "res.h" 
#include <iostream> 
using namespace std; 
 
void fun1() { 
    fun(); 
 
     for (int i = 0; i < 6; i++) { 
          g_szBuffer[i] = ‘a’ + i; 
     } 
     cout<<g_szBuffer<<endl; 

/************************/ 

[cpp] view plaincopy
/***********test2.h**********/ 
void fun2(); 
/************************/ 

[cpp] view plaincopy
/***********test2.cpp**********/ 
#include "test2.h" 
#include "res.h" 
#include <iostream> 
using namespace std; 
 
void fun2() { 
     cout<<g_szBuffer<<endl; 

/************************/ 

[cpp] view plaincopy
/***********main.cpp**********/ 
#include "test1.h" 
#include "test2.h" 
 
int main() { 
     fun1(); 
     fun2(); 
 
     system("PAUSE"); 
     return 0; 

/************************/ 

运行结果如下:

    按我们的直观印象,认为fun1()和fun2()输出的结果都为abcdef,可实际上fun2()输出的确是初始值。然后我们再跟踪调试,发现res、test1、test2中g_szBuffer的地址都不一样,分别为0x0041a020、0x0041a084、0x0041a040,这就解释了为什么不一样。

    注:一般定义static 全局变量时,都把它放在.cpp文件中而不是.h文件中,这样就不会给其他编译单元造成不必要的信息污染。

(6)全局常量(const)
    const单独使用时,其特性与static一样(每个编译单元中地址都不一样,不过因为是常量,也不能修改,所以就没有多大关系)。
    const与extern一起使用时,其特性与extern一样。

[cpp] view plaincopy
extern const char g_szBuffer[];      //写入 .h中 
const char g_szBuffer[] = "123456"; // 写入.cpp中 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值