zoukankan      html  css  js  c++  java
  • C++解析(13):临时对象与const对象

    0.目录

    1.临时对象

    2.const对象

    3.类成员

    4.小结

    1.临时对象

    一个有趣的问题——下面的程序输出什么?为什么?

    #include <stdio.h>
    
    class Test {
        int mi;
    public:
        Test(int i) {
            mi = i;
        }
        Test() {
            Test(0);
        }
        void print() {
            printf("mi = %d
    ", mi);
        }
    };
    
    
    int main()
    {
        Test t;
        
        t.print();
    
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    mi = -1130582912
    [root@bogon Desktop]# ./a.out 
    mi = -763375808
    [root@bogon Desktop]# ./a.out 
    mi = 179227552
    [root@bogon Desktop]# ./a.out 
    mi = 1452105392
    

    发生了什么?

    • 程序意图——在Test()中以0作为参数调用Test(int i),将成员变量mi的初始值设置为0
    • 运行结果——成员变量mi的值为随机值

    构造函数是一个特殊的函数:

    • 是否可以直接调用
    • 是否可以在构造函数中调用构造函数
    • 直接调用构造函数的行为是什么?

    答案:

    • 直接调用构造函数将产生一个临时对象
    • 临时对象的生命周期只有一条语句的时间
    • 临时对象的作用域只在一条语句中
    • 临时对象是C++中值得警惕的灰色地带

    在实际的开发工程中,也许构造函数体的逻辑非常复杂,没有必要哪个构造函数都写一遍,采用代码复用的思想,提供一个私有的init()函数进行初始设置:

    #include <stdio.h>
    
    class Test {
        int mi;
        
        void init(int i)
        {
            mi = i;
        }
    public:
        Test(int i) {
            init(i);
        }
        Test() {
            init(0);
        }
        void print() {
            printf("mi = %d
    ", mi);
        }
    };
    
    
    int main()
    {
        Test t;
        
        t.print();
    
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    mi = 0
    

    进一步证明临时对象的生命周期只有一条语句的时间:

    #include <stdio.h>
    
    class Test {
        int mi;
        
        void init(int i)
        {
            mi = i;
        }
    public:
        Test(int i) {
            printf("Test(int i)
    ");
            init(i);
        }
        Test() {
            printf("Test()
    ");
            init(0);
        }
        void print() {
            printf("mi = %d
    ", mi);
        }
        ~Test() {
            printf("~Test()
    ");
        }
    };
    
    
    int main()
    {
        printf("main begin
    ");
        
        Test().print();
        Test(10).print();
        
        printf("main end
    ");
    
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    main begin
    Test()
    mi = 0
    ~Test()
    Test(int i)
    mi = 10
    ~Test()
    main end
    

    编译器的行为——现代C++编译器在不影响最终执行结果的前提下会尽力减少临时对象的产生!!!

    #include <stdio.h>
    
    class Test
    {
        int mi;
    public:
        Test(int i)
        {
            printf("Test(int i) : %d
    ", i);
            mi = i;
        }
        Test(const Test& t)
        {
            printf("Test(const Test& t) : %d
    ", t.mi);
            mi = t.mi;
        }
        Test()
        {
            printf("Test()
    ");
            mi = 0;
        }
        int print()
        {
            printf("mi = %d
    ", mi);
        }
        ~Test()
        {
            printf("~Test()
    ");
        }
    };
    
    Test func()
    {
        return Test(20);
    }
    
    int main()
    {
        Test t = Test(10); // ==> Test t = 10;
        Test tt = func();  // ==> Test tt = Test(20); ==> Test tt = 20;
        
        t.print();
        tt.print();
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    Test(int i) : 10
    Test(int i) : 20
    mi = 10
    mi = 20
    ~Test()
    ~Test()
    

    可以看到,并没有调用拷贝构造函数。因为现代C++编译器在不影响最终执行结果的前提下,会尽力减少临时对象的产生。因此直接将Test t = Test(10);优化为Test t = 10;,这样就少调用了一次构造函数,提高了程序效率!

    2.const对象

    关于const对象的疑问——const关键字能否修饰类的对象?如果可以,有什么特性

    • const关键字能够修饰对象
    • const修饰的对象为只读对象
    • 只读对象的成员变量不允许被改变
    • 只读对象是编译阶段的概念运行时无效

    const修饰的对象为只读对象,不能修改成员变量,否则将报错:

    #include <stdio.h>
    
    class Test
    {
    public:
        int mj;
        Test(int j);
    };
    
    Test::Test(int j)
    {
        mj = j;
    }
    
    int main()
    {
        const Test t(1);
        
        t.mj = 2;
        
        return 0;
    }
    

    报错信息为:

    [root@bogon Desktop]# g++ test.cpp
    test.cpp: In function ‘int main()’:
    test.cpp:19: error: assignment of data-member ‘Test::mj’ in read-only structure
    

    C++中的const成员函数

    • const对象只能调用const的成员函数
    • const成员函数中只能调用const成员函数
    • const成员函数中不能直接改写成员变量的值

    const成员函数的定义:
    Type ClassName::function(Type p) const

    类中的函数声明与实际函数定义中都必须带const关键字。

    const对象只能调用const的成员函数,否则会报错:

    #include <stdio.h>
    
    class Test
    {
        int mi;
    public:
        Test(int i);
        Test(const Test& t);
        int getMi()const; // 函数声明处加上const
    };
    
    Test::Test(int i)
    {
        mi = i;
    }
    
    Test::Test(const Test& t)
    {
        
    }
        
    int Test::getMi()const // 函数定义处加上const
    {
        return mi;
    }
    
    int main()
    {
        const Test t(1);
        
        printf("t.getMi() = %d
    ", t.getMi());
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    t.getMi() = 1
    

    3.类成员

    关于类成员的疑问——成员函数成员变量都是隶属于具体对象的吗?
    面向对象的角度:

    • 对象由属性成员变量)和方法成员函数)构成

    程序运行的角度:

    • 对象由数据函数构成:
      1. 数据可以位于全局数据区
      2. 函数只能位于代码段

    结论:

    • 每一个对象拥有自己独立的属性(成员变量)
    • 所有的对象共享类的方法(成员函数)
    • 方法能够直接访问对象的属性
    • 方法中的隐藏参数 this 用于指代当前对象

    示例:

    #include <stdio.h>
    
    class Test
    {
        int mi;
    public:
        int mj;
        Test(int i);
        Test(const Test& t);
        int getMi();
        void print();
    };
    
    Test::Test(int i)
    {
        mi = i;
    }
    
    Test::Test(const Test& t)
    {
        mi = t.mi;
    }
        
    int Test::getMi()
    {
        return mi;
    }
    
    void Test::print()
    {
        printf("this = %p
    ", this);
    }
    
    int main()
    {
        Test t1(1);
        Test t2(2);
        Test t3(3);
        
        printf("t1.getMi() = %d
    ", t1.getMi());
        printf("&t1 = %p
    ", &t1);
        t1.print();
        
        printf("t2.getMi() = %d
    ", t2.getMi());
        printf("&t2 = %p
    ", &t2);
        t2.print();
        
        printf("t3.getMi() = %d
    ", t3.getMi());
        printf("&t3 = %p
    ", &t3);
        t3.print();
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    t1.getMi() = 1
    &t1 = 0x7fffe0431e30
    this = 0x7fffe0431e30
    t2.getMi() = 2
    &t2 = 0x7fffe0431e20
    this = 0x7fffe0431e20
    t3.getMi() = 3
    &t3 = 0x7fffe0431e10
    this = 0x7fffe0431e10
    

    可以看到,在类的成员函数当中,有一个隐含的参数,这个隐含的参数是一个指针,并且这个指针的值就是调用这个函数所对应的对象的地址。

    4.小结

    • 直接调用构造函数将产生一个临时对象
    • 临时对象是性能的瓶颈,也是bug的来源之一
    • 现代C++编译器会尽力会避开临时对象
    • 实际工程开发中需要人为的避开临时对象
    • const关键字能够修饰对象,得到只读对象
    • 只读对象只能调用const成员函数
    • 所有对象共享类的成员函数
    • 隐藏的this指针用于表示当前对象
  • 相关阅读:
    CentOS 7 Docker基本特性
    linux go环境安装
    Docker部署Golang
    高可用Redis服务架构分析与搭建
    linux常用基本命令
    docker容器
    Linux 配置网络连接
    自定义泛型委托对象
    sql游标使用
    CSS 实用实例
  • 原文地址:https://www.cnblogs.com/PyLearn/p/10082088.html
Copyright © 2011-2022 走看看