C++变量作用域、生存期、存储类别

写C、C++代码的小伙伴一定在头疼变量的作用域、生存期、存储类别问题。什么静态、外部、寄存器、局部、全局搞得一头雾水。今天咱们就来梳理一下他们的变态关系(什么不得了的事情???

1、变量的作用域

说白了,作用域就是一个”代码块“,也就是大括号包裹的那一段东西。包括函数体、控制语句块这些。大家应该都有所耳闻。

#include<stdio.h>
int x = 5; // 全局变量
int main() {
    printf("%d ",x);
    int x = 6; // 局部变量
    printf("%d ",x); // 输出结果是5还是6?
    return 0;
}

这段代码算很经典了。它展示了不同定义位置的变量的作用域。

首先一个输出肯定是5,毫无悬念。但是下面那个就不同了,因为在main函数中又出来一个同名的局部变量。C++遵循向上覆盖原则,在一个代码块的子块中定义的变量,覆盖掉原块中定义的变量。所以,下面那个输出应该是6。一旦在子块中定义了同名变量,这个块外部的变量就不可见了。所以,实际编程要避免这种情况

但C++提供了一种叫做“作用域运算符”的东西,也就是::。这个运算符可以使得全局变量重新可见。比如我在x = 6定以后想再用全局变量的值,就可以这样写:printf("%d", ::x);这样输出就是5了。这个运算符大家应该都见过,在类和namespace中经常使用。但不管怎么说,除非是在class和namespace中,其它的情况应该严格避免内外作用域的变量重名

说了这么多,作用域的概念给大家:作用域,就是从变量定义开始到所在代码块或文件结束为止,对编译器可见的范围

如果对上面这些概念不理解,就往下看,局部变量和全局变量的概念。

2、局部变量和全局变量

局部变量,是指在一个函数内部定义的变量。它的作用域从定义(或声明)开始,到函数结束。它只对函数本身可见,对函数外部不可见。任何手段都无法访问函数内部的变量。因为除了main函数之外,其它函数都不是程序实体,它只提供了一个模块运行的模板,里面的变量只是为了这个函数服务,函数外部引用它没有意义。

为了方便往下讲,先说一下生存期的概念。作用域是个静态概念,表示变量的可见范围,是对编译器而言的,而生存期是动态概念,表示变量在内存中的创建、使用、销毁过程,是运行时概念。生存期是指变量从创建到被操作系统回收的这一段时间。缩句的话,作用域是代码范围,生存期是时间范围。

回到正题,局部变量的生存期,就是从函数创建它开始,到函数调用完毕,被操作系统回收这一段时间

我们知道,一个进程有一段栈空间,函数调用的过程是用栈管理的。栈保存函数的参数、局部变量、返回地址。当一个函数被调用时,CPU首先把当前执行地址保存到一个特殊寄存器中,作为这一函数的返回地址。然后,CPU跳转到函数入口地址,栈顶指针扩展一帧,栈空间随之增长。把刚才的返回地址保存到栈中,并从栈中加载参数。之后就是执行过程。执行完毕后,从栈中取出返回地址,CPU跳回这一地址。最后栈顶指针回退一帧,这一函数的栈空间将被释放,局部变量也就随之销毁。

上面这一段看不懂也没关系,如果你们学过汇编语言和操作系统,就会明白这是一个怎样的过程。总之,需要记住局部变量是在函数调用并执行到定义语句时创建,函数返回时销毁

全局变量,是指不在任何函数内定义的变量。它定义在文件的顶层,对任何函数都可见(我们从现在起,假设任何函数中没有和全局变量重名的局部变量)。

全局变量的作用域是从定义开始到这个文件结束。其实这种说法不精确,因为全局变量可以被其他文件调用,这就是外部变量,我们稍后再讲。

和局部变量不同,全局变量是在进程创建(注意进程创建和main函数调用是两个概念,进程创建包括代码加载、数据加载、内存空间分配过程,是操作系统完成的,而main函数是进程调用的)时同时创建的。所以,全局变量在main函数调用之前就已经存在了。一个进程的地址空间分为代码段、数据段、用户段,代码段就是机器指令(参看冯诺依曼体系结构),数据段就是我们所说的全局变量,而用户段是供进程运行过程中动态分配内存的。我们刚才说的栈空间就在用户段。所以说,全局变量的存储位置和局部变量完全不同。

全局变量又分为已初始化的和未初始化的。已初始化的全局变量,操作系统会自动为其初始化值,放在数据段前部,而未初始化的全局变量,则会放在数据段的后部,并自动清零。所以,你看到未初始化的全局变量初始值都是0。

全局变量的生存期从进程创建开始,一直到进程运行完毕,所有内存被操作系统回收位置为止。

另外,在不是函数的代码块中创建的局部变量,也是类似的。比如

int s = 0;
for (int i = 0; i < 10; i++) {
    s += i;
    int t = s;
}
printf("%d %d %d", i, s, t); // 错误,i和t只对for循环体可见

3、变量存储类别

说完了生存期、作用域的概念,我们再来看变量存储类别。

变量的存储类别分为自动(auto),静态(static),外部(extern)和寄存器(register)四种。

3.1、自动变量

注意,虽然叫auto变量,但是,auto关键字在C++11中已经不再是“自动变量”的意思,而是“自动类型推断”。所以,不要试图用auto关键字来创建自动变量

比如:

auto a = 1;
printf("%d\n", a); // a的类型自动推断为整型
auto int b = 2; // 错误,auto是指自动类型推断,不可以与类型标识符连用
printf("%d\n", b);

我们刚才所说的变量,都是自动变量。所谓自动变量,就是按照操作系统的内存分配和回收规则来管理的变量,不需要程序员干预,动态管理。如果是局部变量,则由栈空间保存,全局变量则由数据段保存。

3.2、静态变量和外部变量

这个是难点中的难点,大家一定要仔细看。

3.2.1、静态局部变量

我们刚才说的局部变量,是自动局部变量,也就是说作用域和生存期是捆绑的,一旦出了作用域,则销毁,不再可见。而静态局部变量则不同。它和全局变量一样,是放在数据段中的,只初始化一次,下次调用这个函数时,保留原来的值,继续使用。如下:

int count() {
    static int cnt = 0;
    return ++cnt;
}

如果cnt是个自动局部变量,则每次调用函数的时候,都要初始化为0,所以每次的返回值都是1。但定义成静态局部变量就不同了,它在函数调用结束后并不销毁,保留原来的值,下一次调用时,初始化语句是不起作用的,所以它返回的是函数被调用的次数。

本质上,静态局部变量和全局变量的生存期完全相同,只是作用域不同。刚才说了,作用域是相对于编译器来说的,所以静态局部变量编译器提供的“语法糖”,为避免全局变量重名造成干扰而引入的机制。

3.2.2、外部变量和静态全局变量

在讲静态全局变量之前,我们来先讲一下外部变量。

我们刚才说的全局变量,仅仅是对本文件可见吗?No,它对其他文件也可见。我们编译C++程序的时候,往往不止一个源文件,如果1.cpp要调用2.cpp的某个全局变量,怎么办呢?

答案就是,使用extern声明。比如main.cpp的内容:

#include<stdio.h>
int a[20];
void operate(); // 函数声明
int main() {
    operate();
    for (int i = 0; i < 20; i++) {
        printf("%d ",a[i]);
    }
}

operate.cpp中内容:

extern int a[20]; // extern声明
void operate() {
    a[0] = 0;
    a[1] = 1;
    for (int i = 2; i < 20; i++) {
        a[i] = a[i-1] + a[i-2];
    }
}

编译命令:g++ main.cpp operate.cpp -o main.exe

运行结果:0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181

是不是很神奇?就像一个文件调用其他文件定义的函数一样,其他文件的全局变量也是可以使用的,只不过需要声明为extern。所以,外部变量是这样一个概念。把它类比成函数声明就行。

说完了外部变量,我们来说静态全局变量。刚才说到,全局变量可以被其他文件所引用,如果我们不想让它被其他文件引用,怎么办?答案就是,把这个全局变量加上static修饰。这时,static已经不再是静态的概念,而是阻止其它文件调用的意思。所以,静态全局变量这个称呼太过直译,有点误导。正确的叫法是文件内变量。

最初C++团队想增加一个intern关键字来表示这种变量,但是遭到广大程序员强烈反对,本着关键字能少即少的原则,就对static进行了“重载”,赋予了这么一个功能。其实我觉得还不如直接叫intern,static总是让人误会。

static也可以修饰函数,这表示,其他文件不可调用这个函数。如果把operate.cpp中的operate()定义成static void operate(),则编译报错。

3.3、寄存器变量

说完了最难的,咱们来放松一下。寄存器是CPU的一些存储部件,它用来存放立即就要参加CPU运算的数据。它的读写速度是纳秒级别,比内存、缓存都快至少2~3个数量级。

用register关键字声明的变量,叫做寄存器变量。表示,通知编译器把这个变量直接放在寄存器中而不是内存中,对一些频繁访问的变量,这样可以加快速度。但是,仅仅是通知编译器这样做,而编译器可能不会理会你的声明,而仍然把变量放在内存中。因为寄存器个数非常少,我们PC机的64位x86 CPU,只有8个通用寄存器,其中还有两个不是存放普通数据的。即使是寄存器较多的MIPS CPU,也只有32个。

另外需要注意的一点,就是寄存器变量不可以使用取地址运算符&,也不可以用指针指向它。因为寄存器没有地址,指针只能保存内存地址值,而不能保存寄存器。

4、类中的static

4.1、静态字段

类中的字段分为普通字段和静态字段。普通字段在类对象创建时被创建,它的生存期和类对象是捆绑的,同生共死。而静态字段,不依赖于对象创建,它被保存在数据段中。引用静态字段,可以用成员运算符,也可以用作用域运算符。

class Point {
private:
    int x;
    int y;
public:
    static int cnt; // 其实这是不安全的,容易被篡改,应该设为private。但是作为一个例子来讲解静态字段
    Point(int xx, int yy) {
        cnt++; // 对当前拥有的对象个数计数
        x = xx;
        y = yy;
    }
    ~Point() {
        cnt--;
    }
};
static int Point::cnt = 0; // 静态变量初始化必须在类外进行,除非它是const的

int main() {
    printf("%d ", Point::cnt); // 类名访问
    Point p(3, 4);
    printf("%d ", p.cnt); // 对象名访问
    Point *pp = new Point(5, 6);
    printf("%d ", pp->cnt); // 指针访问
    delete pp;
    printf("%d ", Point::cnt);
    return 0;
}

4.2、静态方法

静态方法和普通方法不同,它也是所有类对象共享的。可以通过类名调用,也可以通过对象名调用。把刚才的类改一下:

class Point {
private:
    int x;
    int y;
    static int cnt;
public:
    Point(int xx, int yy) {
        cnt++; // 对当前拥有的对象个数计数
        x = xx;
        y = yy;
    }
    static int getCnt() {
        return cnt;
    }
    /*
    static int getX() {
        return x;
    }
    */ // 这个函数是错误的,静态方法不可引用非静态字段
    ~Point() {
        cnt--;
    }
};
static int Point::cnt = 0;

int main() {
    printf("%d ", Point::getCnt()); // 类名访问
    Point p(3, 4);
    printf("%d ", p.getCnt()); // 对象名访问
    Point *pp = new Point(5, 6);
    printf("%d ", pp->getCnt()); // 指针访问
    delete pp;
    printf("%d ", Point::getCnt());
    return 0;
}

注意,静态方法不可访问非静态字段,也不可调用非静态方法。

4.3、静态内部类

内部类分为普通内部类和静态内部类两种。它们唯一的不同就是,普通内部类是外部类的“奴隶”,它不仅受制于外部类,而且一切字段不能对外部类隐藏,即使是private。而静态内部类则不同,相当于放在某个类内部的外部类,private字段是不可访问的。所以静态内部类也叫嵌套类。和Java是一样的。

比如:

class aaa {
private:
    class bbb {
    private:
        int a;
    };
    static class ccc {
    private:
        int a; // a对外不可见
    };
public:
    bbb bb;
    ccc cc;
    int bbbb() {
        return bb.a; // 正确
    }
    int cccc() {
        return cc.a; // 错误
    }
};

原文地址:https://www.cnblogs.com/wancong3/p/10714451.html

时间: 2024-09-29 09:26:58

C++变量作用域、生存期、存储类别的相关文章

C++变量的存储类别与作用域

总结一下C++中变量的存储类别以及变量的作用域. (1)标示符的存储类别决定了标示符在内存中存在的时间(我们可以理解标示符就是确定一个变量的符号,也就是我们所说的变量名) 二:存储类别 (1)静态存储类别:静态存数类别变量(我们简称静态变量),从程序的开始处就存在,其生命期伴随整个程序. (2)自动存储类别:当变量时自动存储类别时,变量在进入到定义它们的程序快时定义它,在离开它们所在的程序块(作用域)时销毁它,因此成为自动变量.其中关键字auto和register用来声明自动类型的变量, 三:自

C语言变量的存储类别

我们知道,从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量. 从另一个角度,从变量值存在的作时间(即生存期)角度来分,可以分为静态存储方式和动态存储方式. 静态存储方式:是指在程序运行期间分配固定的存储空间的方式. 动态存储方式:是在程序运行期间根据需要进行动态的分配存储空间的方式. 用户存储空间可以分为三个部分: 程序区: 静态存储区: 动态存储区. 全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序行完毕就释放.在程序执行过程中它们占据固定的存储单元,而不

[C++]变量存储类别

C++中共有四种存储类别标识符:auto/static/register/extern 1.auto 函数或分程序内定义的变量(包括形参)可以定义为auto(自动变量).如果不指定存储类别,则隐式定义为auto. 例如,函数类有如下定义: auto int x , y ; 等价于: int x , y ; 2.static 除了形参,可以将局部变量和全局变量定义为静态变量.用static标识符. static int a;//a是全局静态变量 f() {static int b = 1;}//b

变量存储类别

变量定义的一般形式: 存储类别 数据类型 变量名 存储类别指的是数据在内存中存储的方法.存储方法分为静态存储和动态存储两大类.标准C语言为变量.常量和函数定义了4种存储类型:extern.auto.static.register.根据变量的存储类别,可以知道变量的作用域和存储期.这4种存储类型可分为两种生存期限:永久的(在整个程序执行期都存在)和临时的(暂时保存在堆栈和寄存器中).extern和static用来标识永久生存期限的“变量和函数”,而anto和register用来标识临时生存期限的"

变量的存储类别

全局变量和局部变量 在函数之外定义的变量称为外部变量,也是全局变量:在函数内部定义的变量称为局部变量,它只在本函数范围内有效.全局变量在程序的全部执行过程都占用存储单元,而局部变量只是在调用到该函数的时候才回去动态的给局部变量分配内存空间. 从 变量的作用域角度来分,可以分为全局变量(生命周期是该文件开始到结束)和局部变量(离开该方法或者复合语句就无效):从变量值存在的时间即生命周期来分可以分为 静态存储方式和动态存储方式. 所谓静态存储方式是指在程序运行期间有系统分配指定的存储空间的方式,而动

c语言 变量的存储类别以及对应的内存分配?

<h4><strong>1.变量的存储类别</strong></h4>从变量值存在的角度来分,可以分为静态存储方式和动态存储方式.所谓静态存储方式指在程序运行期间由系统分配固定的存储空间的方式(<strong>程序开始执行时分配,在程序完毕时释放,在程序过程中它们占据国定的存储单元,而不是动态分配和释放</strong>).而动态存储方式在运行期间根据需要进行动态存储方式(<strong>在程序过程中申请和释放的一些空间&

C语言入门(十四)变量的作用域和存储类型

变量的作用域和存储类型 一.作用域和生存期 C程序的标识符作用域有三种:局部.全局.文件.标识符的作用域决定了程序中的哪些语句可以使用它,换句话说,就是标识符在程序其他部分的可见性.通常,标识符的作用域都是通过它在程序中的位置隐式说明的. 1.局部作用域 前面各个例子中的变量都是局部作用域,他们都是声明在函数内部,无法被其他函数的代码所访问.函数的形式参数的作用域也是局部的,它们的作用范围仅限于函数内部所用的语句块. void add(int); main() { int num=5; add(

[C++程序设计]变量的存储类别

全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储单元,程序执行完毕就释放这些空间.在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放. 在动态存储区中存放以下数据: 1函数形式参数.在调用函数时给形参分配存储空间.2函数中的自 动变量(未加static声明的局部变量,详见后面的 介绍).3函数调用时的现场保护和返回地址等.对以上这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间.在程序执行过程中,这种分配和释放是动态的,如果在一个程序中两次调用同

变量类型(接C变量作用域,生存期,链接特性)

自动变量 自动存储类型,特点:自动存储期,块作用域,无链接.默认情况下,在块级作用域中或函数头中的变量属于自动存储类型的变量.当然,也可以受用关键字"auto"特别声明,一般用处不大. 示例: // hiding.c -- variables in blocks #include<stdio.h> int main() { int x = 30; // original x printf("x in outer block: %d at %p\n", x