经典问题解析二(十九)

今天我们来探讨下当程序中存在多个对象时,如何确定这些对象的析构顺序?那么单个对象创建时构造函数的调用顺序是:a> 调用父类的构造过程(我们会在后面进行讲解);b> 调用成员变量的构造函数(调用顺序与生命顺序相同);c> 调用类自身的构造函数。析构函数与对应构造函数的调用顺序相反。当多个对象析构时,析构顺序与构造顺序相反。

下来我们以代码为例进行说明

#include <stdio.h>

class Member
{
    const char* ms;
public:
    Member(const char* s)
    {
        printf("Member(const char* s): %s\n", s);
        
        ms = s;
    }
    ~Member()
    {
        printf("~Member(): %s\n", ms);
    }
};

class Test
{
    Member mB;
    Member mA;
public:
    Test() : mA("mA"), mB("mB")
    {
        printf("Test()\n");
    }
    ~Test()
    {
        printf("~Test()\n");
    }
};

Member gA("gA")

int main()
{
    Test t;
    
    return 0;
}

我们先来分析下,首先我们定义两个类 Member 和 Test,在类 Test 中定义了两个类成员 Member。当程序流往下执行时,首先会创建全局类 Member gA,下来是类 Member mB,mA;注意这块它们的顺序和声明的顺序是相同的。最后才是创建类 Test 本身,后面析构的顺序和构造顺序是相反的,我们来看看编译结果

我们看到结果和我们分析的是一致的。那么对于栈对象和全局对象,便类似于入栈与出栈的顺序,最后构造的对象是最先被析构的。堆对象的析构发生在使用 delete 的时候,与 delete 的使用顺序相关!!

我们之前学习了 const 关键字,它可以修饰变量及函数。那么它是否可以修饰类的对象呢?如果可以的话,有何特性呢?const 关键字是可以修饰对象的,它修饰的对象为只读对象。只读对象的成员变量不允许被改变,只读对象是编译阶段的概念,运行时无效。我们下来来讲下 C++ 中的 const 成员函数:a> const 对象只能调用 const 成员函数;b> const 成员函数中只能调用 const 成员函数;c> const 成员函数中不能直接改写成员变量的值。const 成员函数的定义:Type ClassName::function(Type p) const。类中的函数声明与实际函数定义中都必须带 const 关键字。

下来我们还是以示例代码进行分析说明

#include <stdio.h>

class Test
{
    int mi;
public:
    Test(int i);
    int getMI();
};

Test::Test(int i)
{
    mi = i;
}

int Test::getMI()
{
    return mi;
}

int main()
{
    const Test t(1);

    printf("t.getMI() = %d\n", t.getMI());
        
    return 0;
}

我们将 main 函数中的 Test 对象定义为 const 的,然后输出 mi 的值,我们直接编译下,看看能否编译通过呢

我们看到直接报错了,它说这是个 const 类型的对象。我们前面讲过,const 类型的对象只能调用 const 成员函数,所以我们在 getMI 函数的声明和实现中都加上 const,再来编译看看

我们看到编译通过,并且完美运行。那么我们说过 const 成员函数是在其中不能改变值的,我们试试在 getMI 函数中赋值 mi 为 2,看看编译是否通过呢

它说这个成员在只读的函数中,不能被改变。那么成员函数和成员变量都隶属于具体对象的吗?从面向对象的角度,对象由属性(成员变量)和方法(成员函数)构成。从程序运行的角度,对象由数据和函数构成,数据可以位于栈,堆和全局数据区,但函数只能位于代码段。结论便是:a> 每一个对象拥有自己独立的属性(成员变量);b> 所有的对象共享类的方法(成员函数);c> 方法能够直接访问对象的属性;d> 方法中的隐藏参数 this 用于指代当前对象。

我们以代码为例进行说明

#include <stdio.h>

class Test
{
    int mi;
public:
    int mj;
    Test(int i);
    Test(const Test& t);
    int getMI() const;
    void print();
};

Test::Test(int i)
{
    mi = i;
}

Test::Test(const Test& t)
{
    mi = t.mi;
}

int Test::getMI() const
{
    return mi;
}

void Test::print()
{
    printf("this = %p\n", this);
}

int main()
{
    Test t1(1);
    Test t2(2);
    Test t3(3);
    
    printf("t1.getMi() = %d\n", t1.getMI());
    printf("&t1 = %p\n", &t1);
    t1.print();
    
    printf("t2.getMi() = %d\n", t2.getMI());
    printf("&t2 = %p\n", &t2);
    t2.print();
    
    printf("t3.getMi() = %d\n", t3.getMI());
    printf("&t3 = %p\n", &t3);
    t3.print();
    
    return 0;
}

我们定义了三个对象,分别打印出它们的 mi 的值,它们本身的地址以及对应的 this 指针的地址,看看编译结果

那么它们为什么能知道自己定义的 mi 的值对应的是多少呢?就是因为每个对象有个隐藏的 this 指针,而且通过打印可以看出这个 this 指针和他们本身的地址是相同的。可能有的小伙伴对拷贝构造函数中的 mi 的赋值有疑问,可以这样直接进行赋值嘛?这个 t.mi 不是私有成员嘛?通过运行,编译器告诉了我们答案,是可以这样写的。因为成员函数是位于代码段的,因此成员函数是唯一的。也就是说,成员函数只有一套,它能够直接访问对应类的成员变量。所以这块是不冲突的。通过今天对特定问题的学习,总结如下:1、对象的析构顺序与构造顺序相反;2、const 关键字能够修饰对象,得到的是只读对象,只读对象只能调用 const 成员函数;3、所有对象共享类的成员函数;5、隐藏的 this 指针用于表示当前对象。

欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083。

原文地址:http://blog.51cto.com/12810168/2118370

时间: 2024-08-04 08:45:41

经典问题解析二(十九)的相关文章

Android学习笔记二十九之SwipeRefreshLayout、RecyclerView和CardView

Android学习笔记二十九之SwipeRefreshLayout.RecyclerView和CardView 前面我们介绍了AlertDialog和几个常用的Dialog,ProgressDialog进度条提示框.DatePickerDialog日期选择对话框和TimePickerDialog时间选择对话框.这一节我们介绍几个新的API控件SwipeRefreshLayout.RecyclerView和CardView,这几个API控件都是google在Android5.0推出的.下面我们来学

攻城狮在路上(叁)Linux(二十九)--- 完整备份工具:dump以及restore

一.dump命令: 该命令既可以针对整个文件系统进行备份,也可以仅针对目录来备份.还可以指定不同的备份等级(-0~-9共10个等级). dump -W:列出在/etc/fstab中具有dump设置的分区是否备份过. 命令格式: dump [-Suvj] [-level] [-f 备份文件] 待备份数据 参数说明: -S:仅列出后面的待备份数据所需要的磁盘空间大小. -u:将这次dump的时间记录到/etc/dumpdates文件中. -v:将dump的文件过程显示出来. -j:加入bzip2的支

企业搜索引擎开发之连接器connector(二十九)

在哪里调用监控器管理对象snapshotRepositoryMonitorManager的start方法及stop方法,然后又在哪里调用CheckpointAndChangeQueue对象的resume方法获取List<CheckpointAndChange> guaranteedChanges集合 下面跟踪到DiffingConnectorTraversalManager类的相关方法,在该类实现的方法中,调用了监控器管理对象snapshotRepositoryMonitorManager的相

[原创]ActionScript3游戏中的图像编程(连载二十九)

2.2.2 Photoshop投影大小的模拟 投影没有之前那么浓了,但是跟Photoshop里的效果差别还挺大,因为在Photoshop里我们还设置了另外一个属性:大小. Flash里似乎找不到它的影子,我们用排除法来进行定位,Photoshop投影样式的大小属性以像素为单位,Flash投影滤镜的选项只有距离和那对被“手铐”扣住的模糊属性符合条件,而Photoshop里也有一个距离,所以我们定位到模糊属性(图 2.15). 图 2.15 Flash投影的模糊属性 分别调整Photoshop的大小

Welcome to Swift (苹果官方Swift文档初译与注解二十九)---209~218页(第四章-- 流程控制)

Break break语句会立刻结束整个流程控制的执行.break语句可以在switch语句或者循环语句中帮助你提前结束循环或者switch的执行. Break in a Loop Statement  (循环语句中的break) 当在循环语句中使用break,会立刻结束循环的执行,并且跳转到循环体之后的第一行代码.循环不会再遍历执行了. Break in a Switch Statement (switch语句的break) 当在switch语句中使用break,break会立刻结速switc

每日算法之二十九:Search in Rotated Sorted Array

在一个经过旋转后的有序数组中查找一个目标元素. Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2). You are given a target value to search. If found in the array return its index, otherwise return -1.

ActionScript3游戏中的图像编程(连载二十九)

2.2.2 Photoshop投影大小的模拟 投影没有之前那么浓了,但是跟Photoshop里的效果差别还挺大,因为在Photoshop里我们还设置了另外一个属性:大小. Flash里似乎找不到它的影子,我们用排除法来进行定位,Photoshop投影样式的大小属性以像素为单位,Flash投影滤镜的选项只有距离和那对被"手铐"扣住的模糊属性符合条件,而Photoshop里也有一个距离,所以我们定位到模糊属性(图 2.15). 图 2.15 Flash投影的模糊属性 分别调整Photosh

【Unity 3D】学习笔记二十九:游戏实例——简单小地图制作

任何的学习,光看不练是学不好的.所以这次就总结回顾下怎么制作MMROPG类游戏中的小地图.在MMROPG类游戏里,主角在游戏世界里走动时,一般在屏幕右上角都会有一个区域来显示当前游戏场景的小地图.主角在游戏世界里走动,小地图里代表着主角的小标记也会随之移动.那怎么实现咧? 首先需要确定两个贴图,第一个是右上角的小地图背景贴图,应该是从Y轴俯视向下截取主角所在的位置大地图.第二个就是主角的位置大贴图.在本例中,因为没有学习unity地图制作,所以地图用一个面对象代替,主角用立方体代替,使用GUI来

二十九、linux常用命令(一)

vim是打开vim编辑器,别的编辑器还有vi(功能没有vim 强大),nano,emacs等等,感觉还是vim最强大,其次是vi,别的就要差一些了. 我听我们老师说,用图形界面本身已经会被高手笑了,如果打开一个gpedit或者kwrite那就废了......常用的命令 ls,列出当前目录下的文件,ls -l是列出详细信息,ls -a列出隐藏文件. cd,更改目录.clear,清屏命令.reset,重置终端. startx,启动图形界面.fdisk -l,查看硬盘分区. ps aux,列出系统进程

29、蛤蟆的数据结构笔记之二十九数组之硬币抛掷模拟

29.蛤蟆的数据结构笔记之二十九数组之硬币抛掷模拟 本篇名言:"人生是各种不同的变故.循环不已的痛苦和欢乐组成的.那种永远不变的蓝天只存在于心灵中间,向现实的人生去要求未免是奢望.-- 巴尔扎克" 欢迎转载,转载请标明出处: 1.  硬币抛掷 如果抛掷硬币N次,看到头像的期望值是N/2次,但实际值也可能是0~N次,在程序中进行M次试验,M和N都在代码中定义.它使用一个数组f来跟踪出现"i次头像"的概率,其中0≤j≤N.然后打印试验结果的柱状图,每出现10次用1个星号