C++ Primer 学习笔记_23_类与数据抽象(9)--四种对象生存期和作用域、static 用法总结

C++ Primer 学习笔记_23_类与数据抽象(9)--四种对象生存期和作用域、static 用法总结

前言:

从上图可知,程序占用的内存被分了以下几部分.

(1)、栈区(stack)

存放函数的参数值,局部变量的值等,内存的分配是连续的。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动被销毁,其特点是效率高,但空间大小有限

注意:通常栈空间容量比较小,一般是1MB~2MB,所以体积比较大的对象不适合在栈中分配。

(2)、堆区(heap)

由malloc系列函数或new操作符动态分配的内存,其生命周期由free或delete决定。类似于链表,在内存中的分布不是连续的,它们是不同区域的内存块通过指针链接起来的。在没有释放之前一直存在,直到程序结束,其特点是使用灵活,空间比较大,但容易出错。

(3)、静态区(全局区)(static)

保存自动全局变量和static变量(包括static全局和局部变量)。静态区的内容在总个程序的生命周期内都存在,由编译器在编译的时候分配,程序结束后由系统释放。

(4)、文字常量区

常量字符串就是放在这里的。 程序结束后由系统释放

(5)、程序代码区

存放函数体的二进制代码。

一、四种对象生存期和作用域

(1)栈对象

隐含调用构造函数(程序中没有显式调用)

(2)堆对象

隐含调用构造函数(程序中没有显式调用),要显式释放

(3)全局对象、静态全局对象

全局对象的构造先于main函数

已初始化的全局变量或静态全局对象存储于.data段中

未初始化的全局变量或静态全局对象存储于.bss段中

(4)静态局部对象

已初始化的静态局部变量存储于.data段中

未初始化的静态局部变量存储于.bss段中

注意:箭头的方向的意思是,栈是由高地址向低地址扩展,堆是由低地址向高地址扩展

【例子】

#include <iostream>
using namespace std;

class Test
{
public:
    Test(int n) : n_(n)
    {
        cout << "Test " << n_ << " ..." << endl;
    }
    ~Test()
    {
        cout << "~Test " << n_ << " ..." << endl;
    }
private:
    int n_;
};

int n;   // 未初始化的全局变量,初始值为0。n存储于.bss段中。(block started by symbol)
int n2 = 100;   // 已初始化的全局变量,初始值为100。n2存储于.data段中。
Test g(100);    // 全局对象的构造先于main函数
static Test g2(200);

int main(void)
{
    cout << "Entering main ..." << endl;

    Test t(10);     // 栈上创建的对象,在生存期结束的时候自动释放
    {
        Test t(20);
    }

    {
        Test *t3 = new Test(30);        // 堆上创建的对象,要显式释放
        delete t3;
    }

    {
        static int n3;          // n3存储于.bss段中  (编译期初始化)
        static int n4 = 100;    // n4存储于.data段中 (编译期初始化)
        static Test t4(333);    // t4对象运行期初始化   .data段
    }
    cout << "Exiting main ..." << endl;
}

运行结果:

Test 100 ...

Test 200 ...

Entering main ...

Test 10 ...

Test 20 ...

~Test 20 ...

Test 30 ...

~Test 30 ...

Test 333 ...

Exiting main ...

~Test 10 ...

~Test 333 ...

~Test 200 ...

~Test 100 ...

二、static 用法总结

(1). 用于函数内部修饰变量,即函数内的静态变量。这种变量的生存期长于该函数,使得函数具有一定的“状态”。使用静态变量的函数一般是不可重入的,也不是线程安全的,比如strtok(3)。

(2). 用在文件级别(函数体之外),修饰变量或函数,表示该变量或函数只在本文件可见,其他文件看不到也访问不到该变量或函数。

C语言的这两种用法很明确,一般也不容易混淆。

由于C++引入了类,在保持与C语言兼容的同时,static关键字又有了两种新用法:

(3).用于修饰类的数据成员,即所谓“静态成员”。这种数据成员的生存期大于class的对象(实例/instance)。静态数据成员是每个class有一份,普通数据成员是每个instance 有一份。

(4). 用于修饰class的成员函数,即所谓“静态成员函数”。这种成员函数只能访问静态成员和其他静态成员函数,不能访问非静态成员和非静态成员函数。

1、static在程序中三种用法

(1)static在函数或全局只对当前文件下可见

(2)static在类中只属于一个类而不属于特定对象,或对象的变量和函数

(3)statci在头文件中作用域只在定义了该头文件的c/cpp文件中,其他有同一头文件是不可见的。(在网上看到一个说法,不一定对,这样定义有一定的危害性:头文件包含多次,变量就定义了多少次,容易重复定义,因此不建议在头文件定义static。)

2、不考虑类,static的作用

(1)第一个作用:隐藏

当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。比如,同时编译两个源文件,a.cpp和main.cpp,下面时a.cpp的内容:

#include <iostream>
using namespace std;

char a = 'A';
void msg()
{
        cout << "Hello World" << endl;
}

则在a.cpp中定义的全局变量a和函数msg都能在main.cpp中使用,extern。

但是如果加了static,就会对其他源文件隐藏。例如在a和msg的定义前加上static,main.cpp就看不到它们了。

【例子】

在一个cpp文件中,定义了一个static类型的全局变量,下面的描述中正确的是()

A、只能在该cpp所在的编译模块中使用该变量

B、该变量的值是不可改变的

C、该变量不能在类的成员函数中引用

D、这种变量只能时基本类型(如int,char)不能是C++类型

解答:A

对于函数来讲,static的作用仅限于隐藏,而对于变量,satic还有下面两个作用。

(2)第二个作用:默认初始化0

包括未初始化的全局静态变量与局部静态变量。其实,未初始化的全局变量也具备这一属性。因为未初始化的全局变量和未初始化的静态变量是存储在同一块区域内的BSS段。在BSS段中,内存中所有的字节默认值都是0x00.

【例】

下列输出的值是什么?

#include <iostream>
#include <string>
using namespace std;

string str1;
static int a;
int b[2];
int main()
{
    string str2;
    int c;
    int d[2];

    cout << str1 << endl;
    cout << str2 << endl;
    cout << a << endl;
    cout << b[0] << " " << b[1] << endl;
    cout << c << endl;
    cout << d[0] << " " << d[1] << endl;
    return 0;
}

解答:str1和str2为string类型,自动调用string的默认构造函数将各个元素初始化为空字符串;a是全局变量,初始化为0;b是全局数组,各元素初始化为0;c是局部变量,其值为垃圾值;d是局部数组,各元素的值也是垃圾值。

(3)第三个作用:保持局部变量内容的持久

函数内局部变量,当调用时就存在,退出函数时就消失,但静态局部变量虽然在函数内定义,但静态局部变量始终存在着,也就是说它的生存期为整个源程序。其特点是只进行一次初始化且具有“记忆性“。

静态局部变量的生存期虽然为整个源程序,但是其作用域仍与局部变量相同。即只能在定义该变量的函数使用该变量,退出该函数后,尽管该变量还继续存在,但不能使用它,而作用域外还可以重新定义该变量。

【例1】

在某一个函数中使用static定义的局部变量,该变量只进行一次初始化且具有“记忆性“。()(判断题)

解答:正确。

【例2】

以下论述正确的是()(多选)

A、static全局变量只能在定义的文件中使用,普通全局变量在所有文件中都可以使用

B、static局部变量存储在bss段或数据(data)段中,可以保持其上次的赋值。普通局部变量在堆栈中,不能保持其上次赋值

C、static函数只在当前源文件中可使用,普通函数在其他文件中都可以使用。

D、static变量若不初始化,其值未定义

解答:ABC。B中,初始化的静态变量存储在data段(数据段)中,未初始化的静态变量存储在相邻的另一块区域(BSS段)中。

【例3】

下列对静态变量的描述正确的是()(多选)

A、静态局部变量在静态存储区内分配单元

B、静态外部变量可以赋初值,也可以不赋初值

C、静态外部变量的作用与外部变量相同

D、静态局部变量在函数调用结束时,仍保持其值,不会随着消失

E、静态局部变量只赋一次初值

解答:ABDE。C中如果说静态局部变量的作用域与局部变量相同则正确,注意题目的变形。

【例4】

void fun()
{
    static int val;
    ......
}

以上中,变量val的内存地址位于()。

A、已初始化数据段

B、未初始化数据段

C、堆

D、栈

解答:B。未初始化数据段,即BBS段。如果初始化了,则在已初始化数据段,即data段。

3、类中static的作用

C++重用了static这个关键字,并赋予它不同的含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数。

(1)静态数据成员

static数据成员独立于该类的任意对象而存在;也就是说当某个类的实例修改了该静态数据成员变量,其修改值也为该类的其他所有实例所见。

静态数据成员和普通数据成员一样遵从public,protected,private访问规则。

静态数据成员也存储在全局(静态)存储区。静态数据成员定义时要分配空间,所以不能在类声明中定义。static数据成员必须在类定义体的外部定义(正好一次)。

【例1】

下列有关静态数据成员的描述中,正确的是()

A、静态数据成员可以在类体内初始化

B、静态数据成员不可以被类对象调用

C、静态数据成员不受private控制符作用

D、静态数据成员可以直接用类名调用

解答:D。可以通过类对象调用,也可以通过类名调用。

【例2】

C++中关于对象成员内存分布的描述正确的是()

A、不管该类(class)被产生多少对象(object),静态成员变量永远只有一个实例,而且在没有对象实例的情况下已经存在

B、非静态成员数据在类中的排列顺序将和其被声明的顺序相同,任何中间介入的静态成员都不会被放进对象的内存布局中

C、在同一访问段(也就是private, public, protected等区间段内),数据成员的排列符合“较晚出现的成员在对象中有较高的内存地址“

D、带有虚函数的类对象占用内存大小跟虚函数的个数成正比

解答:ABC。

类中数据成员的布局情况是:

——非静态成员的类对象中排列顺序和声明顺序一致,任何在其中间声明的静态成员都不会被放进对象布局中。

——静态数据成员存放在程序的全局(静态)区中,和个别类对象无关。

C++标准规定中,在同一个访问块即private, public, protected等区间段中,成员的排列只需符合较晚出现的成员在类对象中有较高地址即可。也就是说,并不一定要连续排列。什么的东西可能会介于被声明的members之间呢?比如说members的边界调整时需要填充的一些字节等。

(2)静态成员函数

与普通成员函数相比,静态成员函数由于不与任何的对象相关联,因此它不具有this指针。因而它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数。它只能调用其余的静态成员函数与访问静态数据成员。

因为static成员不是任何对象的组成部分,所以static成员函数不能被声明为const。

最后,static成员函数不能被声明为虚函数、volatile。

class base
{
    virtual static void fun1();  //Error
    static void fun2() const;  //Error
    static void fun3() volatile;  //Error
};

关于静态成员函数,可以总结以下两点:

第一,静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和静态成员函数。静态成员函数不能访问非静态成员函数和非静态数据成员,非静态数据成员可以任意地访问静态成员函数和静态数据成员。

第二,由于没有this指针的额外开销,因此静态成员函数与类的非静态成员函数相比速度会有少许的增长。

【例】

下列关于一个类的静态成员的描述中,不正确的是()

A、该类的对象共享其静态成员变量的值

B、静态成员变量可被该类的所有方法访问

C、该类的静态方法只能访问该类的静态成员

D、该类的静态数据成员变量的值不可修改

解答:D。

(3)使用static成员变量而不是全局变量有三个优点

第一,static成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。

第二,可以实施封装。static成员可以是私有成员,而全局对象不可以。

第三,通过阅读程序容易看出static成员是与特定类关联的。这种可见性可清晰地显示程序员的意图。

参考:

C++ primer 第四版

Effective C++ 3rd

http://blog.csdn.net/jnu_simba/article/details/9237581

http://blog.csdn.net/zjf280441589/article/details/24730905

C++编程规范

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-27 07:20:50

C++ Primer 学习笔记_23_类与数据抽象(9)--四种对象生存期和作用域、static 用法总结的相关文章

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员)、拷贝构造函数

C++ Primer 学习笔记_19_类与数据抽象(5)_初始化列表(const和引用成员).拷贝构造函数  从概念上将,可以认为构造函数分为两个阶段执行: 1)初始化阶段: 2)普通的计算阶段.计算阶段由构造函数函数体中的所有语句组成. 一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 1.对象成员及其初始化 <span style="font-size:14px;">#include <iostream> using namespace std;

C++ Primer 学习笔记_56_类与数据抽象 --消息处理示例

复制控制 --消息处理示例 说明: 有些类为了做一些工作需要对复制进行控制.为了给出这样的例子,我们将概略定义两个类,这两个类可用于邮件处理应用程序.Message类和 Folder类分别表示电子邮件(或其他)消息和消息所出现的目录,一个给定消息可以出现在多个目录中.Message上有 save和 remove操作,用于在指定Folder中保存或删除该消息. 数据结构: 对每个Message,我们并不是在每个Folder中都存放一个副本,而是使每个Message保存一个指针集(set),set中

C++ Primer 学习笔记_57_类与数据抽象 --管理指针成员

复制控制 --管理指针成员 引言: 包含指针的类需要特别注意复制控制,原因是复制指针时只是复制了指针中的地址,而不会复制指针指向的对象! 将一个指针复制到另一个指针时,两个指针指向同一对象.当两个指针指向同一对象时,可能使用任一指针改变基础对象.类似地,很可能一个指针删除了一对象时,另一指针的用户还认为基础对象仍然存在.指针成员默认具有与指针对象同样的行为. 大多数C++类采用以下三种方法之一管理指针成员: 1)指针成员采取常规指针型行为:这样的类具有指针的所有缺陷但无需特殊的复制控制! 2)类

C++ Primer 学习笔记_55_类与数据抽象 --析构函数

复制控制 --析构函数 引言: 在构造函数中分配了资源之后,需要一个对应操作自动回收或释放资源.析构函数就是这样的一个特殊函数,它可以完成所需的资源回收,作为类构造函数的补充. 1.何时调用析构函数 撤销类对象时会自动调用析构函数: Sales_item *p = new Sales_item; { Sales_item item(*p); //调用复制构造函数 delete p; //调用指针p的析构函数 } //调用对象item的析构函数 动态分配的对象只有在指向该对象的指针被删除时才撤销,

C++ Primer 学习笔记_53_类与数据抽象 --友元、static成员

类 --友元.static成员 一.友元 友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类(对未被授权的函数或类,则阻止其访问):友元的声明以关键字friend开始,但是它只能出现在类定义的内部.友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受其声明出现部分的访问控制影响. [最佳实践] 通常,将友元声明成组的放在类定义的开始或结尾是个好主意! 1.友元关系:一个例子 假设一个窗口管理类Window_Mgr可能需要访问由其管理的Screen对象的内部

C++ Primer 学习笔记_54_类与数据抽象 --复制构造函数、赋值操作符

复制控制 --复制构造函数.赋值操作符 引言: 当定义一个新类型时,需要显式或隐式地指定复制.赋值和撤销该类型的对象时会发生什么– 复制构造函数.赋值操作符和析构函数的作用!      复制构造函数:具有单个形参,该形参(常用const修饰)是对该类类型的引用.当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式的使用复制构造函数:当将该类型的对象传递给函数或者从函数返回该类型的对象时,将隐式使用复制构造函数.     析构函数:作为构造函数的互补,当对象超出作用域或动态分配的对象被删除

C++ Primer 学习笔记_24_类与数据抽象(10)--static 与单例模式、auto_ptr与单例模式、const成员函数、const 对象、mutable修饰符

C++ Primer 学习笔记_24_类与数据抽象(10)--static 与单例模式.auto_ptr与单例模式.const成员函数.const 对象.mutable修饰符 前言 [例]写出面向对象的五个基本原则? 解答:单一职责原则,开放封闭原则,依赖倒置原则,接口隔离原则和里氏替换原则 里氏替换原则:子类型必须能够替换他们的基类型. 设计模式分为三种类型:创建型模式.结构型模式和行为型模式 一.static 与单例模式 1.单例模式 单例模式的意图:保证一个类仅有一个实例,并提供一个访问它

C++ Primer 学习笔记_16_类与数据抽象(2)_隐含的this指针

C++ Primer 学习笔记_16_类与数据抽象(2)_隐含的this指针 1.引言 在前面提到过,成员函数具有一个附加的隐含形参,即指向该类对象的一个指针.这个隐含形参命名为this. 2.返回*this 成员函数有一个隐含的附加形参,即指向该对象的指针,这个隐含的形参叫做this指针(编译器自动传递)使用this指针保证了每个对象可以拥有不同数值的数据成员,但处理这些成员的代码可以被所有对象共享.成员函数是只读的代码,由所有对象共享,并不占对象的存储空间,因为this指针指向当前对象,所以

C++ Primer 学习笔记_17_类与数据抽象(3)_类作用域

C++ Primer 学习笔记_17_类与数据抽象(3)_类作用域 引言: 每个类都定义了自己的新作用域与唯一的类型.即使两个类具有完全相同的成员列表,它们也是不同的类型.每个类的成员不同与任何其他类(或任何其他作用域)的成员. 一.类作用域中的名字查找 1)首先,在使用该名字的块中查找名字的声明.只考虑在该项使用之前声明的名字. 2)如果在1)中找不到,则在包围的作用域中查找. 如果找不到任何声明,则出错. 类的定义实际上是在两个阶段中处理: 1)首先,编译器声明: 2)只有在所有成员出现之后