C++ extern声明外部文件所定义的变量,与cmake的CMakeLists.txt中set(SOURCE ...)的关系

demo工程的目录结构:

HaypinsMBP:trainCmake haypin$ tree .
.
├── C2.cpp
├── C2.h
├── CMakeLists.txt
├── Strclass.cpp
├── Strclass.h
├── build
│   └── build.sh
├── main.cpp
└── temp.cpp

1 directory, 8 files

C2.h:

#pragma once
#include<string>
class C2{
    private:
        std::string m_str;
    public:
        C2(std::string val);
        ~C2(void);
        std::string Getm_str();
};

C2.cpp:

#include <iostream>
#include "C2.h"
using namespace std;
string globvar2="learn harder!";
C2::C2(std::string val ):m_str(val){
    // m_str=val;
};
C2::~C2(){}
std::string C2::Getm_str(){
    return m_str;
};

Strclass.h:

#pragma once
#include<string>
// class C2;
#include "C2.h"
using namespace std;
class Cstrclass{
    private:
        static string sattr;
        string m_str;
        C2 m_C2; 
        //如果只是在头文件单独声明class C2则cmake编译报错:error: field has incomplete type
    public:
        Cstrclass(string val,string val2);
        ~Cstrclass(void);
        static string staticMeth();//静态方法只能操作类静态字段与其他静态方法,相当于Python的类方法
        string Meth();//方法也可以操作全局作用域的变量,此时相当于Python的静态方法
        void print();
};

Strclass.cpp:

#include <iostream>
// #include "C2.h"
#include "Strclass.h"
string globvar="haha";
string Cstrclass::sattr="heihei";
//类静态字段必须在全局作用域初始化,扮演着全局作用域变量的角色,只不过是封装在类内
Cstrclass::Cstrclass(string val ,string val2):m_str(val) \
    ,m_C2(val2){
    // m_str=val;
};
Cstrclass::~Cstrclass(){};
string Cstrclass::staticMeth(){
    return sattr;
};
string Cstrclass::Meth(){
    return globvar;
};
void Cstrclass::print(){
    cout<<m_str<<"-"<<m_C2.Getm_str()<<"\n";
};

以及"单独"的temp.cpp:

#include<string>
std::string globvar3("brave");
extern const std::string globcstr("glob const string");

main.cpp:

#include <iostream>
#include <string>
#include "Strclass.h"
using namespace std;
struct SubType{
    string name;
    SubType(string val){
        name=val;
    };
    string Name(){
        return name;
    };
};
template <typename T>
string Getname(T obj){
    return obj.Name(); //模板类T的实例需要提供Name()方法
}
extern string globvar;  //定义在Strclass.cpp中,也在CMakeLists.txt的set(SOURCE ...)中
extern string globvar2; //定义在C2.cpp中,也在CMakeLists.txt的set(SOURCE ...)中
extern string globvar3; //定义在temp.cpp中,也在CMakeLists.txt的set(SOURCE ...)中
extern const std::string globcstr; //定义在temp.cpp中,也在CMakeLists.txt的set(SOURCE ...)中
int main(int argc,char *argv[]){
    SubType obj=SubType("ahah");
    cout<<Getname(obj)<<"\n";   //ahah

    Cstrclass objcstr=Cstrclass("heihei","haha");
    cout<<objcstr.staticMeth()<<"\n";   //通过实例调用类静态方法
    cout<<Cstrclass::staticMeth()<<"\n";//通过类名调用类静态方法

    objcstr.print();
    cout<<globvar<<"\n";
    cout<<globvar2<<"\n";
    cout<<globvar3<<"\n";
}

其中extern声明的globvar定义在Strclass.cpp中,Strclass.h被main.cpp直接包含;globvar2定义在C2.cpp中,C2.h被Strclass.h直接包含;globvar3定义在temp.cpp中,temp.cpp不被main.cpp直接或间接包含;三个全局变量定义所在的文件都被CMakeLists.txt的set(SOURCE ....),也就是设置源文件的语法所包含,从而在编译extern声明时能在这些源文件中进行名字查找

CMakeLists.txt:

cmake_minimum_required(VERSION 3.0)
project(main)

set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g)
set(SOURCE main.cpp Strclass.h Strclass.cpp C2.h C2.cpp temp.cpp)

add_executable(main ${SOURCE})

./build/build.sh:

#!/bin/bash
cd build
cmake ..
make -j8
mv main ../

编译执行:

HaypinsMBP:trainCmake haypin$ sudo bash ./build/build.sh
-- Configuring done
-- Generating done
-- Build files have been written to: /Users/haypin/VScode/cpptest/trainCmake/build
Scanning dependencies of target main
[ 20%] Building CXX object CMakeFiles/main.dir/temp.cpp.o
[ 40%] Linking CXX executable main
[100%] Built target main
HaypinsMBP:trainCmake haypin$ ./main
ahah
heihei
heihei
heihei-haha
haha
learn harder!
brave
HaypinsMBP:trainCmake haypin$ 

最典型的是temp.cpp中的globvar3,temp.cpp没有被任何其他文件所包含,只出现在CMakeLists.txt的set(SOURCE ...)中,cmake编译时才会在作为源文件的temp.cpp中进行"extern string globvar3;"的名字查找。当注释掉CMakeLists.txt的set(SOURCE ...)中的temp.cpp后再编译,则会报未定义的符号错:


[ 25%] Linking CXX executable main
Undefined symbols for architecture x86_64:
  "_globvar3", referenced from:
      _main in main.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [main] Error 1
make[1]: *** [CMakeFiles/main.dir/all] Error 2
make: *** [all] Error 2
mv: main: No such file or directory
HaypinsMBP:trainCmake haypin$ 

并且,extern外部声明变量不能在源文件中重复定义,比如当在Strclass.cpp中重复定义globvar3后编译,会报重复的符号错:

[ 20%] Linking CXX executable main
duplicate symbol '_globvar3' in:
    CMakeFiles/main.dir/Strclass.cpp.o
    CMakeFiles/main.dir/temp.cpp.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make[2]: *** [main] Error 1
make[1]: *** [CMakeFiles/main.dir/all] Error 2
make: *** [all] Error 2
mv: main: No such file or directory
HaypinsMBP:trainCmake haypin$

再补充一个知识点:static全局变量具有文件作用域,也就是说static全局变量只在其定义的文件内可见,对其他文件不可见。从而,如果在Strclass.cpp中重复定义globvar3但加以static修饰,则main.cpp中的extern std::string globvar3从源文件进行名字查找时会屏蔽调Strclass.cpp的static std::string globvar3,并能正确查找到temp.cpp中的std::string globvar3;

再补充一个知识点:来自《C++ primer》(第五版)P54:对const常量再文件间共享,也就是只在一个文件中定义const,而再其他多个文件中声明并使用它。此时对该const常量不管是声明还是定义都要添加extern关键字,也就是demo中的globcstr。

当使用#pragma once指令时,它会告诉编译器只编译一次当前头文件,以避免多次包含同一个头文件。然而,有时候#pragma once无法解决包含(include)报错的问题。在这种情况下,可以考虑使用#ifndef方式来避免重复包含头文件。 #ifndef方式的实现方法是在头文件的开头使用预处理指令#ifndef和#define创建一个宏,然后在头文件的结尾使用#endif结束。这样,当头文件被多次包含时,宏会起到条件判断的作用,只有第一次包含时才会执行宏内的代码。 以下是一个简单的示例: ```cpp #ifndef XLOG_H #define XLOG_H #include <string> std::string LogTest(const std::string& szLog) { return szLog; } #endif ``` 在这个示例,首先使用#ifndef和#define创建了一个名为XLOG_H的宏,然后在头文件的结尾使用#endif结束。这样,在包含该头文件时,如果XLOG_H宏未定义,则会执行宏内的代码。 通过使用#ifndef方式,可以在头文件被多次包含时避免报错。希望这个解决方法对你有帮助!<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [函数放在头文件时被多次包含,编译报错:重定义](https://blog.csdn.net/qq_34552942/article/details/124291590)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [#pragma once用法](https://blog.csdn.net/weixin_45254208/article/details/103223369)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值