声明和定义:这里我们将普通数据变量和函数统称变量。从内存分配角度来说,声明和定义的区别在于声明一个变量不会分配内存,而定义一个变量会分配内存。一个变量可以被声明多次,但是只能被定义一次。
基于以上前提,我们可以把声明和定义类比为指针和内存的关系。我们知道,指针其实就是指向内存的一个符号,变量的定义就好比一块内存区域,而声明就好比它的指针,可以有多个指针指向同一个内存区域,而一个指针只能指向一个内存区域,这样就很好理解为什么变量只能被定义一次,如果被定义多次,那就会分配多个内存,这样你通过变量的声明到底去找哪块内存区域呢,这会是个问题。
int data; //对于数据来说,声明和定义往往是同时存在这样既声明了data同时也定义了data,
extern int data;//只声明未定义
static关键字
static关键字的作用有很多,声明静态全局变量,类的静态成员等。这里主要讨论他在修饰全局变量时与extern的区别。有两点需要注意:
1、static修饰全局变量时,声明和定义是同时给出的;
2、static的全局作用域只是自身编译单元。
编译单元:
每个cpp就是一个编译单元,每个编译单元相互之间是独立且相互不知的。一个编译单元(Translation Unit)是指一个.cpp文件以及这所include的所有.h文件,.h文件里面的代码将会被扩展到包含它的.cpp文件里,然后编译器编译该.cpp文件为一个.obj文件,后者拥有PE(Portable Executable,即Windows可执行文件)文件格式,并且本身包含的就是二进制代码,但是不一定能执行,因为并不能保证其中一定有main函数。当编译器将一个工程里的所有.cpp文件以分离的方式编译完毕后,再由链接器进行链接成为一个.exe或.dll文件。
还有一点需要说明的是:static也可以修饰局部变量,用static修饰的变量都是保存在静态存储区的,当然全局变量不管用不用static修饰都是存在静态存储区,那全局变量用static修饰和不用static修饰有什么区别呢?区别就在于“全局”的范围是整个工程,还是自身编译单元。
//test1.h
static char g[7] = "123456";//static声明和定义同时给出
void fun1();
//test1.cpp
#include "test1.h"
#include <iostream>
void fun1()
{
//g[0] = '9';
std::cout << g << std::endl;
}
//test2.h
void fun2();
//test2.cpp
#include "test2.h"
#include "test1.h"
#include <iostream>
void fun2()
{
std::cout << g << std::endl;
}
main.cpp
#include"test1.h"
#include"test2.h"
int main()
{
fun1();
fun2()
return 0;
}
当fun1()中不使用语句g[0] = ‘9’;以上输出结果是
编译单元test1.cpp中的static char g[7] = “123456”;静态变量在另外一个编译单元test2.cpp中起了作用,似乎只要test2.cpp包含test1.h就好像是可以一起共享的,但实际上是被编译器优化了,如果我们修改fun1()中加上语句g[0] = ‘9’,再看结果:
发现test2.cpp中的g并没有被修改,所以static修饰的只在同一个编译单元是有效的,再另外一个编译单元通过加上所在头文件只是获得了一份拷贝
extern关键字
当extern不与"C"在一起修饰变量或函数时,如在头文件中: extern int g_Int; 它的作用就是声明函数或全局变量的作用范围的关键字,其声明的函数和变量可以在本模块或者其他模块中使用,记住它是一个声明不是定义!也就是说B模块(编译 单元)要是引用模块(编译单元)A中定义的全局变量或函数时,它只要包含A模块的头文件即可, 在编译阶段,模块B虽然找不到该函数或变量,但它不会报错,它会在连接时从模块A生成的目标代码中找到此函数。
//test1.h
static char g[7] = "123456";//extern再头文件先声明
void fun1();
//test1.cpp
char g[7] = "123456";//定义
void fun1()
{
g[0] = '9';
std::cout <<"fun1: "<< g << std::endl;
}
//test2.h
-void fun2();
//test2.cpp
#include "test2.h"
#include "test1.h"
#include <iostream>
void fun2()
{
std::cout << g << std::endl;
}
main.cpp
#include"test1.h"
#include"test2.h"
int main()
{
fun1();
fun2()
return 0;
}
从以上结果可以看到,两边都被修改了,因为extern可以跨编译单元使用,只要包含test1.h的头文件即可,所以extern的范围比static的“范围大”
注意:也可以不使用头文件,既在一个编译单元声明,另外一个编译单元定义就可以了
如:记住 可以多次声明,定义只有一次:
但最好不要这样使用,容易搞混乱
/* test.cpp */
#include <string>
#include <stdio.h>
// 声明g_name
extern std::string g_name;
// 声明和定义void hello()
void hello()
{
printf("hello %s!\n", g_name.c_str());
}
/* main.cpp */
#include <string>
// 声明和定义g_name
std::string g_name;
// 声明void hello()
extern void hello();
int main()
{
g_name = "Handy"
hello();
return 0;
}
未命名空间
1.定义在未命名空间的名字可以直接使用,因为没有它的名字来限定,所以会存在二义性问题
2.如果两个文件都存在未命名空间,则两个空间相互无关
3.未命名空间定义的变量拥有静态声明周期:他们在第一次使用前创建,并且直到程序结束才销毁
4.和其他命名空间不同,未命名的命名空间仅在特定文件有效,其作用范围不会跨个多个不同文件,类似static
哈哈,以下错误
自己测试得到结果:c.h中含有未命名空间成员,若a.h b.h都包含c.h,在头文件a.h或者b.h中修改该成员(好像static也存在这个问题),则大家共同被修改,若在a.cpp包含a.h或者b.cpp包含b.h 在他们cpp中修改则只是拷贝副本,互相不影响,类似static也是这样。
所以猜测在头文件一般不会去修改变量
哈哈,以上错误
其实,若光有a.h或者b.h则与main.cpp一起组成了一个编译单元,所以其实是在一个编译单元中变化,当然可以互相改变,
若a.h有a.cpp包含a.h , b.h有b.cpp包含b.h,则他们是两个编译单元了以及main.cpp的编译单元,当然不能跨编译单元,只是拷贝了副本,各自变化,不影响
int i;
namespace {
int i;
}
int main()
{
i = 0;//发生二义性,未命名空间也没名字,无法区分
}
如果这样就可以
int i;
namespace A{
namespace {
int i;
}
}
int main()
{
A::i = 0;//明确
i = 1;//明确
}