掘根宝典之C++存储持续性,作用域和链接性(自动存储连续性,静态持续变量,关键字static,const的链接性,函数链接性,动态分配链接性)

存储持续性

在C++中,数据的持续性指的是数据存储的生命周期。C++中有多种方式来实现数据的持续性,包括:

  1. 自动存储持续性:在函数内部定义的变量(包括函数参数)的存储持续性是自动的,其生命周期与其所在的作用域相同。当程序执行离开变量的作用域时,自动变量将被销毁C++有两种存储持续性为自动的变量。

  2. 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态,其生命周期从程序开始到程序结束。静态变量在内存中只有一份拷贝,并且在整个程序执行过程中可见C++有3种存储持续性为静态的变量。

  3. 动态存储持续性(动态内存分配):使用new关键字可以在堆上动态地分配内存,使用delete关键字可以释放内存。动态分配的内存在使用后需要手动释放,否则会造成内存泄漏。

  4. 全局变量:全局变量定义在函数外部,其生命周期从程序开始到程序结束。全局变量在内存中只有一份拷贝,并且在整个程序执行过程中可见

  5. 文件存储:数据可以存储在文件中,通过文件操作来读写数据。文件存储的数据持续性取决于文件的生命周期。

这些都是常见的实现数据持续性的方式,具体使用哪种方式取决于数据的需求和程序的设计。

作用域和链接性

作用域和链接是C++中用于控制变量和函数可见性和访问性的重要概念。

  1. 作用域(scope):作用域指的是一个标识符(变量、函数、类等)在程序中可见的范围。C++中有以下几种作用域:

    • 块作用域(block scope):在代码块(花括号括起的一系列语句)中定义的变量和函数只在该块中可见,称为块作用域。
    • 函数作用域(function scope):在函数内部定义的变量和函数只在该函数中可见,称为函数作用域。
    • 文件作用域(file scope):在函数外部定义的变量和函数具有文件作用域,可以在整个文件中访问。
    • 命名空间作用域(namespace scope):命名空间内定义的变量和函数只在该命名空间中可见。
  2. 链接(linkage):链接指的是在不同的编译单元(源文件)中如何访问变量和函数。C++中有以下几种链接类型:

    • 外部链接(external linkage):具有外部链接的变量和函数可以在不同的文件中进行访问和共享。通常用extern关键字来定义具有外部链接的变量和函数。
    • 内部链接(internal linkage):具有内部链接的变量和函数只在同一个文件中可见,不能在其他文件中访问。通常用static关键字来定义具有内部链接的变量和函数。
    • 无链接(no linkage):具有无链接的变量和函数只在其定义的作用域内可见,不能在其他文件中访问。例如,局部变量和函数参数具有无链接。

作用域和链接的概念在编写C++程序时非常重要,可以帮助我们控制变量和函数的可见性,避免命名冲突和访问错误。

自动存储持续性

在函数中声明的函数参数和变量

在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。

话不多说,上例子

void A(int a)
{
a=a-1;
int b=a;
}
int main()
{
int a=1;
int b=2;
A(a);
}

在A函数里面声明了变量b,在main函数里面也声明了变量b,但我们会发现,对A函数里面的b执行的任何操作都不会影响mian()里的b,同理函数参数a也是如此

代码块里定义的变量

代码块:由花括号括起的一系列语句

如果在代码块里定义了变量,则该变量的存在时间和作用域将被限制在该代码块。

话不多说,上例子

#include
using namespace std;
int main()
{
	int a = 9;
	cout << a << endl;//结果是9
//代码块
	{
		int b = 8;
		cout << a << endl;//结果是8
		cout << b << endl;//结果是9
	}

	cout << b << endl;//这不可以
}

从上面这个例子得出,b的作用域被限制在它所在的代码块里,同时我们也发现在代码块外定义的变量a在内部代码块和外部代码块都是可见的

那如果将内部的变量命名为a,使得代码块内部和外部有两个同名的变量,这时情况会如何呢?

话不多说,上例子

#include
using namespace std;
int main()
{
int a=9;
cout<

结果是9 8 9,这表明程序在执行代码块里的语句时,将使用代码块内部定义的a,将a解释为局部变量,新的定义隐藏了旧的定义,新定义可见,旧定义暂时不可见,在程序离开该代码块时,旧定义又重新可见

自动变量的初始化

C++中,自动变量的初始化有两种方式:

  1. 声明的同时进行初始化:可以在变量声明的同时赋初值,例如:
int num = 10;
  1. 在声明后进行赋值:可以在声明变量后的任意位置进行赋值操作,例如:
int num;
num = 10;

需要注意的是,如果在函数内部声明的自动变量没有手动进行初始化操作,那么它的值将是不确定的。因此,为了确保变量的可预测性和正确性,建议在声明变量时进行初始化。

寄存器变量 

在C++中,使用register关键字可以声明寄存器变量。寄存器变量会被存储在寄存器中,而不是内存中,以提高访问速度。然而,现代编译器通常会自动管理寄存器变量的分配,因此register关键字的使用已经不再常见,而且可能被忽略。

关键字register只是显式的指出变量是自动的

要声明寄存器变量,只需在变量声明前加上register关键字,例如:

register int num;

需要注意的是,寄存器变量是有限的,编译器可以根据需要选择是否将变量存储在寄存器中。如果无法将变量分配到寄存器中,变量将自动转换为普通的局部变量。因此,register关键字只是给编译器一个提示,但不能保证变量实际上会被存储在寄存器中。

此外,寄存器变量不能被取地址(即不能使用取地址运算符&),因为寄存器变量没有内存地址。对寄存器变量使用&运算符会导致编译错误。

静态持续变量

和c语言一样,c++也为静态存储持续性变量提供了3种链接性:外部链接性(可在其他文件中访问),内部链接性(只能在当前文件访问),无链接性(只能在当前函数或代码块中访问)。

这3种链接性都在整个程序执行期间存在,与自动变量相比,它们的寿命更长。它们不存放在栈里。编译器会分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在

另外,如果没有显式的初始化静态变量,编译器将把它设置为0。在默认情况下,静态数组和结构将每个元素或成员的所有位都设置为0。

3种静态持续变量的特点

所有静态持续变量在整个程序执行期间都存在

想创建链接性为外部的静态持续变量,必须在代码块的外面声明它;

想创建链接性为内部的静态持续变量,必须在代码块的外面声明它,并用static限定符;

想创建没有链接性的静态持续变量,必须在代码块的内部声明它,并用static限定符

话不多说,上例子

...
int a=100;//外部链接性
static int b=10;//内部链接性
...
int main()
{
...
}
void A(int u)
{
static int c=1;//无链接性
int f=9;
......
}

 a,b,c在整个程序执行期间都存在

a,b的作用域都为整个文件,即在从声明位置到文件结尾的范围都可以被使用,更具体的说,可以在main(),A()里使用它们。它们的区别是b的链接属性是内部,因此只能在包含上述代码的文件里面使用,a的链接属性是外部,因此可以在程序的其他文件中使用它

在A()里声明的变量c的作用域为局部,没有链接性,这意味着只能在A()里调用它,就像变量f一样,与变量f不同的是,即使A()函数没有被执行时,变量c也留在内存里

5种变量存储方式

5种变量存储方式
存储描述 持续性 作用域 链接性 如何声明
自动 自动 代码块 在代码块中
寄存器 自动 代码块 在代码块中,用关键字register
静态,无链接性 静态 代码块 在代码块中,使用关键字static
静态,外部链接性 静态 文件 外部 不在任何函数中
静态,内部链接性 静态 文件 内部 不在任何函数中,使用关键字static

静态变量的初始化

除了默认的零初始化,还可以对静态变量进行常量表达式初始化和动态初始化。

零初始化意味着将变量设置为0,对于标量类型,0将被强制转换为合适的类型。

零初始化和常量表达式初始化被统称为静态初始化,这意味着编译器处理文件(翻译单元)时初始化变量。动态初始化意味着变量将在编译后初始化。

那么初始化形式由什么因素决定呢?

首先所有静态变量都将被零初始化,而不管程序员是否显式地初始化了它。接下来,如果使用了常量表达式初始化了变量,且编译器仅根据文件内容(被包含的头文件)就可计算表达式,编译器将执行常量表达式初始化。必要时,编译器将进行简单计算。如果没有足够的信息,变量将被动态初始化

话不多说,上例子

#include
int x;
int y=5;
const double a=4.0*atan(1.0);

首先x,y,a,被零初始化,然后编译器将计算常量表达式,并将y初始化为5,但要初始化a,必须要调用函数atan(),这需要等到该函数被链接且程序执行时 

外部链接性的静态持续变量

链接性为外部的变量通常简称为外部变量(也叫全局变量),它们的存储持续性为静态,作用域为整个文件。

外部变量是在函数外部定义的,因此对所有函数而言都是外部的(包括main()函数)。可以在main()前面或者头文件里定义它们,可以在文件中位于外部变量定义后的任何函数中使用它。

单定义原则

单定义原则,简而言之就是一个变量只能有一次定义声明

定义声明(简称定义):它为变量分配存储空间;

引用声明(简称声明):它不给变量分配存储空间,因为它引用已有的变量;引用声明使用关键字extern,且不进行初始化;

有的人就想问了啊,如果既用了extern关键字又对变量进行初始化了,那情况会怎么样?

答案是编译器会将这视作定义声明

话不多说,上例子

int a;//定义声明
extern int b;//这是引用声明
extern int c=3;//这被视作定义声明

如果要在多个文件使用外部变量,只需在一个文件中包含该变量的定义(单定义规则),但在使用该变量的其他所有文件中,都必须使用关键字extern声明它

话不多说,上例子(注:A.cpp,B.cpp是同一个程序的)

A.cpp文件(展示定义声明)

extern int a=2;
int b=1;
int c;
//以上都是定义声明
//c会被零初始化
...
int main()
{
...
}

B.cpp文件(展示引用声明)

extern int a;
extern int b;
extern int c;
//以上都是引用声明
...
int main()
{
...
}

注意:在多文件程序里面,可以在一个文件(且只能在一个文件)中定义一个外部变量。使用该变量的其他文件必须用extern声明它 

全局变量和局部变量同名

请注意啊,单定义规则并不意味着不能有多个变量的名称相同。当出现多个同名的变量,会擦出什么样的火花呢?

话不多说,上例子

我们接着上面这个A,B文件啊,假设把B文件设置为下面这样子

extern int a;
extern int b;
extern int c;
//以上都是引用声明
...
void D()
{
cout<

结果很明显啊,新定义的会覆盖掉全局变量,但是出了局部变量的作用域后,全局变量将不会被覆盖

全局变量和局部变量

全局变量和局部变量都有各自的用途和优劣势,具体使用哪种要根据具体情况来决定。

全局变量的好处是可以在程序的任何地方都能访问和使用,可以方便地在多个函数之间共享数据。然而,过多地使用全局变量可能导致程序结构不清晰,难以维护和调试。

局部变量的好处是只在特定的函数或代码块中生效,能够提供更好的封装性和代码的可读性。此外,局部变量在使用完后会自动销毁,不会占用过多的内存空间。然而,局部变量的作用范围有限,只能在定义它的函数或代码块中使用。

因此,一般来说,尽量使用局部变量,只在必要的情况下使用全局变量。使用全局变量时要注意避免滥用,合理命名,避免命名冲突,并且要及时释放不再使用的全局变量。

内部链接性的静态持续变量

将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。

在多文件程序中,内部链接性和外部链接性的差别很有意义

我们看个例子

A.cpp

...
int a=10;
..
int main()
{
...
}

B.cpp

..
int a=10;
..
int main()
{
...
}

这种做法是不行的,因为它违反了单定义的规则

这时我们可以用static关键字

 A.cpp

...
int a=10;
..
int main()
{
...
}

B.cpp

..
static int a=9;
..
int main()
{
...
cout<

这就没有任何问题了,这样子A,B文件里面的a就是不同的变量了

无链接性的静态持续变量

将static限定符用于在代码块里定义的变量,这将导致局部变量的存储持续性为静态的。

这意味着虽然该变量只在该代码块中可用,但它在该代码块不处于活动状态时仍然存在。

因此在两次函数调用中,静态局部变量的值将保持不变。

另外,如果初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化。

下面看个例子

#include
using namespace std;
void A()
{
	static int a;
	cout << a << endl;
	a = a + 1;
	cout << a << endl;
}
int main()
{
	A();
	A();
}

结果是0  1  1  2 ,第一次打印出0是因为零初始化,后面两次调用a的值都是从上一次调用完A函数的a的值的基础上去改动的

我们可以对比一下普通变量

#include
using namespace std;
void B()
{
	 int b=0;
	cout << b << endl;
	b = b + 1;
	cout << b << endl;
}
int main()
{
	B();
	B();
}

结果是0  1  0  1,每次B函数被调用时,自动变量都会被重置为0

​​​​​​​

const变量的链接性

在c++中,const限定符对默认存储类型稍有影响。

在默认情况下全局变量的链接性为外部的,但是const全局变量的链接性为内部的。

也就是说,在C++看来,全局const定义就像使用了static说明符一样。

const int a=0;
//相当于static int a=0;
...
int main()
{
...
}

这也意味着,可以在所有文件中使用相同的声明

我们看个例子

A.cpp

...
const int a=10;
..
int main()
{
...
}

B.cpp

..
const int a=10;
..
int main()
{
...
}

这样子做完全没有问题

为什么可以把const变量定义放在头文件?

因为const的内部链接性,每个文件都有自己的一组常量,而不是所有文件共享一组常量。每个定义都是其所属文件私有的。

把const的链接性改为外部的

这种情况下,必须在所有使用该常量的文件中使用extern关键字来声明它(定义声明和引用声明都要用extern)。不过定义声明只能有一次。

这与常规外部变量不同,定义常规外部变量时,不必使用extern关键字,但是在使用该变量的其他文件中必须使用extern。

我们看个例子

A.cpp

...
extern const int a=10;//必须要extern
..
int main()
{
...
}

B.cpp

..
extern const int a;
..
int main()
{
...
}

这完全ok

在函数和代码块声明const

这时其作用域为代码块,即仅当程序执行该代码块中的代码时,该常量才是可用的(和static一样)

函数链接性

和变量一样,函数也有链接性

和c语言一样,C++不允许在一个函数里定义另一个函数

所有函数的存储连续性都自动为静态的,即在整个程序执行期间一直存在。

在默认情况下,函数的链接性是外部的,即可以在文件间共享。

实际上,可以在函数原型使用关键字extern来指出函数是在另一个文件里定义的,不过这可写可不写

我们还可以用static关键字来将函数的链接性改为内部的,使之只能在同一个文件里使用。不过必须在函数原型和函数定义里同时使用static修饰

...
static int A();
...
static int A()
{
...
}

这意味这个函数只能在这个文件可见,还意味着在别的文件定义同名的函数。和变量一样,在定义静态函数的文件中,静态函数将覆盖外部定义,因此即使在外部定义了同名的函数,该文件仍使用静态函数

同时函数也遵循单定义规则,即只能有一个函数的定义只能放在一个文件里,每个使用该函数的文件都应该包含其函数原型

但是内联函数不遵循这项规则的约束,所以内联函数的定义可以放在头文件里,这样包含了头文件的每个文件都有内联函数的定义

C++在哪里查找函数?

如果该文件中的函数原型指出该函数是静态的,则编译器将只在该文件中查找函数定义;否则,编译器将在所有的程序文件中查找。如果找到两个定义,编译器将发出错误信息,因为每个外部函数只能有一个定义,如果在程序中没有找到,编译器将在库里找。

有的人就想了啊,如果定义了一个与库函数同名的函数,编译器将使用程序员定义的版本,而不是库函数吗?

答案是不可能,因为C++禁止使用标准库函数的名字来做标识符;

动态分配链接性

首先我想说的是编译器使用三块独立的内存,一块用于静态变量,一块用于自动变量,另一块用于动态存储。

动态存储由运算符new和delete控制,而不是由作用域和链接属性控制。因此可以在一个函数中分配内存,而在另外一个函数中将其释放。

在C++中,动态分配的内存具有默认的动态链接属性。这意味着通过关键字new分配的内存是在堆上分配的,并且可以在整个程序的生命周期内可见。

具体来说,动态分配的内存链接属性如下:

  1. 全局可见性:通过new分配的内存可以在整个程序中使用。即使在函数内部分配,也可以在其他函数中访问。

  2. 生命周期:通过new分配的内存一直存在,直到使用delete关键字显式释放它。它的生命周期不受函数或代码块的限制。

  3. 外部链接性:动态分配的内存默认具有外部链接性,这意味着可以在不同的编译单元(源文件)中访问和使用它。只需要在别的文件使用extern限定符作引用声明即可

可以看个例子

A.cpp

..
int main()
{
int*a=new int;
...
}

B.cpp

extern int*a;//引用声明
...
int main()
{
...
}

 这样子就可以在B文件里使用a指针了,我们会发现这一链接属性实则使依靠指针来实现的

需要注意的是,在使用动态分配的内存时,应该遵循良好的内存管理实践,及时释放不再需要的内存,以避免内存泄漏。使用delete关键字释放内存时,需要确保指向该内存的指针不再被使用,否则可能导致未定义的行为。

此外,通过使用智能指针(如std::shared_ptrstd::unique_ptr)来管理动态分配的内存,可以更好地管理内存,避免手动释放内存的疏忽和错误。智能指针会在其生命周期结束时自动释放相关的内存,从而简化了内存管理的工作。

在程序结束时,由new分配的内存都会被释放,不过情况也不总是这样。在不太健壮的系统里,不一定会释放,所以我们尽量使用delete来释放

你可能感兴趣的:(c++,c++,开发语言)