.c .cpp .h

 

基础:

  一个.cpp对应一个obj,一个.obj就是一个编译单元,多个编译单元在链接的时候通过链接器链接,组成一个exe,就是一个程序。如果一个cpp需要另外一个cpp定义的函数,只需要在这个cpp中写上这个函数的声明就可以了,注意在链接的过程中如果有同名的函数或者变量在不同的obj中,程序运行会报错。(实际上在编译的时候,就会提示出现重定义的错误。)在实际的调试过程中发现,变量的重定义在编译的过程中能够立即检测出来,而函数的重定义则不同,如果你首先在一个cpp里面实现该函数的定义,然后运行成功,然后在另外一个cpp函数里面再实现相同的函数的定义,那么程序将不会报错,rebuild all以后,报错,个人认为是编译器优化的原因。

 

避免这种问题的方法:extern 和 static; extern是外部链接的意思,也就是除了这个单元,外部的单元

也是能够访问这个函数的.static 是内部链接,自属于自己单元.

 

心得:对于全局变量尽量不要在.h文件中定义,如果有两个.cpp及以上包含该.h文件,就会出现变量重定义。

         如果要使用全局函数,可以在一个cpp文件中定义该变量,在其他需要的.cpp文件中extern该函数即可             (不需要包含任何头文件)(.h文件在编译的时候会在cpp文件中扩建开)。

      对于static关键字,只能在当前编译单元看到,对外部不可见。

 

记住: 在c++中,变量声明只有在受用extern关键字的时候才成立,其他情况都是定义,而在使用extern的同时给变量赋初值,那么声明变为定义,同时extern被编译器忽略(经过自己验证)。c++变量缺省情况下是全局的,对所有源文件是可见的,如果加上static关键字,会被认为是局部的变量,对外部不可见。

 

1 不要把定义放在.h中,容易导致重复定义

2 尽量把变量定义为static,限制在局部范围内,除非使用全局的。

对于const和typedef是例外,const和typedef默认是局部的,不需要使用static,因此可以在.h文件中使用const变量不会产生重定义,编译器会为每一个引用该.h的源文件申请一份该变量的局部定义,就像该变量定义在该原文件中,但此时和其他使用该变量的源文件是独立的,彼此没有联系。

3 对于static关键字,如果修饰函数内定义的变量或者类内部定义的成员变量时,称为静态变量,修饰其他变量时,称为局部变量。

 

而对于函数,默认情况为extern, 一般函数都为“外部”函数, 

所以在声明函数时不用加"extern". 

如果在一个文件中声明函数时加了"static", 表示这个函数只在这个文件内可见。 

 

<>h文件作用 
1 方便开发:包含一些文件需要的共同的常量,结构,类型定义,函数,变量申明, 
2 提供接口:对一个软件包来说可以提供一个给外界的接口(例如: stdio.h) 

<>h文件里应该有什么 
常量,结构,类型定义,函数,变量申明 

<>h文件不应该有什么 
变量定义, 函数定义 

<>extern问题 
对于变量需要extern, 
对于函数不需要因为函数的缺省状态是extern的.如果一个函数要改变为只在文件内可见,加static 

<>include包含问题 
虽然申明和类型定义可以重复,不过推荐使用条件编译#ifndef _FILENAME_H, #define _FILENAME_H, #endif 

<>应该在那儿包含h文件 
在需要的地方.比如某个提供接口的h文件仅仅被1.c文件需要,那么就在1.c文件里包含 

条件编译目的是防止头文件的重复包含和编译,可能会导致编译速度降低,但不是错误!? 

<think>我们面对的是一个信号采集应用程序的修改需求,要求:1.在数据存盘模式下,对时域数据进行频域分析(FFT变换) 2.在采集的同时显示频谱图需要修改的头文件可能包括:ADDigitView.h, ADDOC.h, ADFrm.h以及对应的源文件:ADDigitView.cpp,ADDOC.cpp,ADFrm.cpp思路: 1.在数据存储模式下,我们通常会将采集的数据保存到文件中。同时,我们需要对实时采集的数据块进行FFT变换,以得到频域数据并显示。2.我们需要在显示时域波形的窗口旁边(或另一个窗口)显示频谱图。3.修改步骤可能包括:a.在ADDOC类(文档类)中增加频域数据存储结构(如复数数组或幅度数组)以及进行FFT计算的函数。b.在ADFrm类(主框架窗口)中增加一个视图用于显示频谱(或者复用现有的ADDigitView,将其分割为时域和频域显示)。c.在ADDigitView类(显示视图)中修改绘制函数,使其能够绘制频域波形(频谱图)。具体修改建议: 一、修改ADDOC.h(文档类头文件)在ADDOC类中,我们需要添加一个用于存储频域数据的缓冲区,并提供一个进行FFT计算的方法。 ```cpp// ADDOC.hclassADDOC: publicCDocument{//...原有代码public://新增:频域数据缓冲区(存储幅度谱)std::vector<double> m_frequencyData;//存储计算后的幅度谱//新增:执行FFT的函数voidPerformFFT(conststd::vector<double>& timeDomainData); };``` 二、修改ADDOC.cpp(文档类实现)在ADDOC.cpp中,实现PerformFFT函数。这里我们使用FFTW库或者自己实现一个FFT函数。注意:为了简化,这里假设使用FFTW库(需要安装并配置),或者可以使用一个简单的DFT实现(效率较低,仅用于演示)。 ```cpp// ADDOC.cpp#include"ADDOC.h" #include<complex> #include<cmath>#include <fftw3.h> //如果使用FFTW库//假设采样率#defineSAMPLE_RATE1000.0//根据实际采样率修改void ADDOC::PerformFFT(conststd::vector<double>& timeDomainData) {intN =timeDomainData.size(); //数据点数//如果数据点数为0,则返回if (N ==0)return;//使用FFTWfftw_complex*in,*out;fftw_plan p;in =(fftw_complex*)fftw_malloc(sizeof(fftw_complex)* N);out =(fftw_complex*)fftw_malloc(sizeof(fftw_complex) *N);//将输入数据复制到in中for (int i=0;i <N;i++){in[i][0] =timeDomainData[i]; //实部in[i][1]=0.0;//虚部}//创建计划p =fftw_plan_dft_1d(N,in,out,FFTW_FORWARD, FFTW_ESTIMATE);//执行FFTfftw_execute(p);//计算幅度谱并存储到m_frequencyDatam_frequencyData.resize(N/2);//通常只保留前N/2个点(对称性)for(inti =0; i< N/2; i++) {doublereal =out[i][0];double imag= out[i][1];m_frequencyData[i] =sqrt(real*real +imag*imag);}//清理fftw_destroy_plan(p);fftw_free(in);fftw_free(out);} ```注意:如果没有FFTW库,可以使用简单的DFT实现,但效率较低。或者使用其他开源库(如KissFFT)。另外,这里只计算了前N/2个点的幅度谱。三、修改ADFrm.h(主框架窗口)在ADFrm类中,我们需要修改主窗口,使其能够同时显示时域和频域视图。我们可以将窗口分割为两个视图。```cpp//ADFrm.hclassADFrm: publicCFrameWnd{//...原有代码protected:CSplitterWnd m_wndSplitter; //分割窗口ADDigitView* m_pLeftView;//左视图(时域)ADDigitView*m_pRightView;//右视图(频域)// ...其他代码}; ```四、修改ADFrm.cpp(主框架窗口实现) 在OnCreateClient函数中创建分割窗口和两个视图。 ```cpp// ADFrm.cpp#include"ADDigitView.h"#include "ADDOC.h"intADFrm::OnCreateClient(LPCREATESTRUCTlpcs, CCreateContext* pContext){//创建分割窗口,1行2列if(!m_wndSplitter.CreateStatic(this,1,2))returnFALSE;//创建左视图(时域显示)if(!m_wndSplitter.CreateView(0,0,RUNTIME_CLASS(ADDigitView),CSize(0,0), pContext))returnFALSE;//创建右视图(频域显示),同样使用ADDigitView类,但我们将通过一个标志来区分显示模式if (!m_wndSplitter.CreateView(0,1, RUNTIME_CLASS(ADDigitView), CSize(0,0),pContext))return FALSE;//获取两个视图的指针m_pLeftView =(ADDigitView*)m_wndSplitter.GetPane(0,0);m_pRightView= (ADDigitView*)m_wndSplitter.GetPane(0,1);//设置左视图为时域显示模式,右视图为频域显示模式m_pLeftView->SetDisplayMode(ADDigitView::TIME_DOMAIN);m_pRightView->SetDisplayMode(ADDigitView::FREQUENCY_DOMAIN);returnTRUE;} ```五、修改ADDigitView.h(视图类)在ADDigitView类中,我们需要添加显示模式的标志,并修改绘制函数以根据不同的模式绘制时域或频域波形。```cpp//ADDigitView.hclassADDigitView :public CView{// ...原有代码public:enumDisplayMode{ TIME_DOMAIN,FREQUENCY_DOMAIN};void SetDisplayMode(DisplayMode mode) {m_displayMode= mode; }protected:DisplayMode m_displayMode;//...其他代码};``` 六、修改ADDigitView.cpp(视图类实现) 修改OnDraw函数,根据显示模式绘制不同的波形。 ```cpp// ADDigitView.cpp#include "ADDOC.h"void ADDigitView::OnDraw(CDC*pDC) {ADDOC*pDoc= GetDocument();ASSERT_VALID(pDoc);//获取视图的客户区大小CRectrect;GetClientRect(&rect);//根据显示模式绘制if(m_displayMode ==TIME_DOMAIN) {//绘制时域波形(原有代码)// ...这里假设原有绘制时域波形的代码} elseif (m_displayMode== FREQUENCY_DOMAIN){//绘制频域波形(频谱图)if(pDoc->m_frequencyData.size() ==0)return;//设置坐标映射pDC->SetMapMode(MM_ANISOTROPIC);pDC->SetWindowExt(1000,1000);pDC->SetViewportExt(rect.Width(),rect.Height());pDC->SetViewportOrg(0,0);//绘制频谱图double maxValue =*std::max_element(pDoc->m_frequencyData.begin(),pDoc->m_frequencyData.end());if (maxValue<=0)maxValue=1.0;//避免除零intn =pDoc->m_frequencyData.size();double xStep =1000.0 /n;for(inti =0; i< n-1; i++) {intx1= static_cast<int>(i* xStep);inty1= static_cast<int>(1000 -(pDoc->m_frequencyData[i]/ maxValue)*900);//留100的边界intx2= static_cast<int>((i+1)* xStep);inty2= static_cast<int>(1000 -(pDoc->m_frequencyData[i+1]/ maxValue)*900);pDC->MoveTo(x1, y1);pDC->LineTo(x2, y2);}}} ```七、在数据采集存盘的同时进行FFT计算并更新显示在数据采集存储的线程中,每当采集到一定数量的数据(比如一个缓冲区),就调用文档类的PerformFFT函数进行FFT计算,并通知两个视图进行更新。假设在ADDOC类中有一个函数用于添加新采集的数据:```cpp//ADDOC.cppvoid ADDOC::AddNewData(conststd::vector<double>&newData) {//将新数据存入时域数据缓冲区(假设原有存储时域数据的结构)//...原有存储时域数据的代码//执行FFT(注意:这里每次来新数据都做整个缓冲区的FFT,实际可能需要优化,如使用滑动窗口等)PerformFFT(/*整个时域数据缓冲区*/);//通知所有视图更新UpdateAllViews(NULL); }``` 注意:这里每次新数据到来就重新计算整个缓冲区的FFT,效率可能较低。实际中可能需要使用滑动窗口或只计算新增数据部分的FFT(如使用重叠保留法等),但这样会复杂很多。另外,也可以设置一个定时器,每隔一定时间对最新的数据进行FFT计算。八、注意事项1. FFT计算的开销较大,需要根据实际数据量和采样率调整策略,避免界面卡顿。 2.频域显示的坐标轴可能需要标定(频率值),这里只绘制了幅度谱,横坐标是频率,纵坐标是幅度。频率范围是0到采样率的一半(奈奎斯特频率)。3.需要安装FFTW库并配置项目设置(包含路径、库路径、链接库)。或者使用其他FFT实现。以上是一个基本的修改方案,具体实现可能需要根据应用程序的现有架构进行调整。§§相关问题§§1.如何优化FFT计算的性能,避免在实时显示时造成卡顿?2.除了FFTW,还有哪些常用的FFT库可以在C++中使用?3.在实时显示频谱图时,如何正确设置频率轴(横轴)的刻度?</think>### 实现信号采集应用程序的频域分析功能 为了在数据存储模式下实现时域数据的频域分析并实时显示频谱图,需要对以下文件进行修改: --- ### 1. **ADDigitView.h 修改** ```cpp // 添加频谱显示相关的成员变量和函数 class ADDigitView : public CView { // ... 原有代码 ... public: // 新增频谱显示标志 BOOL m_bShowSpectrum; // 新增FFT数据缓冲区 std::vector<double> m_fftData; // 新增绘制频谱函数 void DrawSpectrum(CDC* pDC, const CRect& rect); // 新增FFT计算函数 void ComputeFFT(const std::vector<double>& timeData); // ... 原有代码 ... }; ``` --- ### 2. **ADDOC.h 修改** ```cpp class ADDOC : public CDocument { // ... 原有代码 ... public: // 新增实时数据缓冲区 std::vector<double> m_realTimeBuffer; // 新增采样率变量 double m_sampleRate; // 新增FFT计算通知函数 void NotifyFFTCalculation(); // ... 原有代码 ... }; ``` --- ### 3. **ADFrm.h 修改** ```cpp class ADFrm : public CFrameWnd { // ... 原有代码 ... protected: // 新增频谱显示控制按钮 CButton m_btnShowSpectrum; // 新增菜单项ID #define ID_SHOW_SPECTRUM 50001 // ... 原有代码 ... }; ``` --- ### 4. **ADDigitView.cpp 实现** ```cpp // 实现FFT计算 void ADDigitView::ComputeFFT(const std::vector<double>& timeData) { int N = timeData.size(); if (N < 2) return; // 使用FFTW库进行FFT计算 fftw_complex* in = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * N); fftw_complex* out = (fftw_complex*)fftw_malloc(sizeof(fftw_complex) * N); for (int i = 0; i < N; ++i) { in[i][0] = timeData[i]; // 实部 in[i][1] = 0; // 虚部 } fftw_plan p = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE); fftw_execute(p); // 计算幅度谱 m_fftData.resize(N/2); for (int i = 0; i < N/2; ++i) { double re = out[i][0]; double im = out[i][1]; m_fftData[i] = sqrt(re*re + im*im) / N; } fftw_destroy_plan(p); fftw_free(in); fftw_free(out); } // 实现频谱绘制 void ADDigitView::DrawSpectrum(CDC* pDC, const CRect& rect) { if (m_fftData.empty()) return; pDC->MoveTo(rect.left, rect.bottom); double maxVal = *std::max_element(m_fftData.begin(), m_fftData.end()); if (maxVal <= 0) maxVal = 1.0; double xStep = static_cast<double>(rect.Width()) / m_fftData.size(); for (size_t i = 0; i < m_fftData.size(); ++i) { int x = rect.left + static_cast<int>(i * xStep); int y = rect.bottom - static_cast<int>((m_fftData[i] / maxVal) * rect.Height()); pDC->LineTo(x, y); } } // 修改OnDraw函数 void ADDigitView::OnDraw(CDC* pDC) { // ... 原有绘制时域波形的代码 ... // 新增频谱显示 if (m_bShowSpectrum) { CRect rect = GetClientRect(); rect.top = rect.Height() / 2; // 下半部分显示频谱 DrawSpectrum(pDC, rect); } } ``` --- ### 5. **ADDOC.cpp 实现** ```cpp // 在数据采集函数中添加FFT计算触发 void ADDOC::AcquireData(double newSample) { // ... 原有数据采集代码 ... // 将新数据加入缓冲区 m_realTimeBuffer.push_back(newSample); // 当缓冲区达到FFT计算大小时触发计算 if (m_realTimeBuffer.size() >= FFT_SIZE) { NotifyFFTCalculation(); m_realTimeBuffer.clear(); } } // 实现FFT通知函数 void ADDOC::NotifyFFTCalculation() { POSITION pos = GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetNextView(pos); if (pView->IsKindOf(RUNTIME_CLASS(ADDigitView))) { ADDigitView* pDigitView = (ADDigitView*)pView; pDigitView->ComputeFFT(m_realTimeBuffer); pDigitView->Invalidate(); // 请求重绘 } } } ``` --- ### 6. **ADFrm.cpp 实现** ```cpp // 添加频谱显示按钮事件处理 BEGIN_MESSAGE_MAP(ADFrm, CFrameWnd) ON_COMMAND(ID_SHOW_SPECTRUM, OnShowSpectrum) END_MESSAGE_MAP() void ADFrm::OnShowSpectrum() { POSITION pos = GetDocument()->GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetDocument()->GetNextView(pos); if (pView->IsKindOf(RUNTIME_CLASS(ADDigitView))) { ADDigitView* pDigitView = (ADDigitView*)pView; pDigitView->m_bShowSpectrum = !pDigitView->m_bShowSpectrum; pDigitView->Invalidate(); } } } ``` --- ### 7. **需要安装的依赖库** 在项目中添加FFTW库以实现高性能FFT计算: 1. 下载FFTW库:http://www.fftw.org/ 2. 在项目属性中添加包含目录和库目录 3. 链接`fftw3.lib`库文件 --- ### 关键实现说明: 1. **实时FFT计算**:当实时数据缓冲区达到设定大小(如1024点)时触发FFT计算 2. **双区域显示**:视图上半部分显示时域波形,下半部分显示频谱 3. **动态开关**:通过工具栏按钮切换频谱显示状态 4. **性能优化**: - 使用FFTW库进行高效计算 - 设置合理的FFT计算间隔(如每1024点计算一次) - 避免在绘制函数中进行复杂计算 [^1]: 项目设计7个类(图一),另外还有一个基础的graph类,这些类中最主要的就是arrayWDgraph这个类了,其他的类都是为它服务的,关于图的功能和操作也都是在arrayWDgraph这个类里。 [^2]: 返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针。 [^3]: SCTP网络编程接口模型。 [^4]: Oracle 11g grid infrastructure root.sh执行错误诊断。 [^5]: PHP多name值多文件上传实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值