zoukankan      html  css  js  c++  java
  • 对象的使用

    ①、对于特定类型的全体对象而言,有时候可能需要访问一个全局的变量。比如说统计某种类型对象已创建的数量。

    ②、如果我们用全局变量会破坏数据的封装,一般的用户代码都可以修改这个全局变量,这时我们可以用类的静态成员来解决这个问题。

    ③、非static数据成员存在于类类型的每个对象中,static数据成员独立该类的任意对象存在,它是与类关联的对象,不与类对象关联。

    下面用代码来使用static成员实现统计对象已创建的数量:

    接着定一个静态变量:

    编译运行:

    这是为啥呢?

    而对于静态成员还需要有定义性声明,它需要在文件作用域上进行声明,如下:

    编译运行:

    为啥还是出错呢?这是由于静态定义性说明就不用static关键字来修饰了:

    再编译运行:

    由于没对count_静态成员变量初始化,现在想对它进行初始化100,那修改代码如下:

    编译运行:

    从错误提示可以知道,变量的初始化应该在文件作用域中进行,将上面的代码先还原,修改代码如下:

    编译运行:

    而回到这个例子,由于是统计对象个数,则初始化还是还原成0,然后在对象创建的时候+1,销毁对象时候-1,如下:

    接下来测试一下代码:

    编译运行:

    ④、static成员优点

    • static成员的名字是在类的作用域中,因此可以避免与其它类成员或全局对象名字冲突。
    • 可以实施封装,static成员可以是私有的,而全局对象不可以。
      将它改为私有,修改代码如下:

      编译运行:

      这时则需要向外暴露一个可以访问它的方法,如下:



      其输出结果一样。
    • 阅读程序容易看出static成员与某个类相关联,这种可见性可以清晰地反映程序员的意图。

    ⑤、static成员的定义:static成员需要在类定义体外进行初始化与定义

    ⑥、特殊的整型static const成员:整型static const成员可以在类定义体中初始化,该成员可以不在类体外进行定义

     
    编译运行:

    当然也可以在类外部进行声明:

    编译运行:

    类体中和类外中只能对其初始化一次,不能同时都初始化,所以应该这样改:

    编译运行:

    如果换成double呢?

    编译运行:

    【注意】:这种语法在VC6中是不允许的。

    ①、static成员函数没有this指针。

    ②、非静态成员函数可以访问静态成员。

    ③、静态成员函数不可以访问非静态成员。

    用代码来说明下:

    编译:

    而出现这个错误的原因就是没有隐含的this指针指向某个对象,而y_是属于某个对象的,所以就无法访问。

    编译运行:

     

    【面试时可能会问到它】

    ①、类大小计算遵循前面学过的结构体对齐原则。

    ②、类的大小与数据成员有关与成员函数无关。

    ③、类的大小与静态数据成员无关。

    ④、虚函数对类的大小的影响。【关于虚函数后面会学到,会占4个字节,增加一个虚表指针,先做个了解】

    ⑤、虚继承对类的大小的影响。

    下面用代码来试下:

    编译运行:

    关于对象的作用域与生存期实际上在之前已经学过了,这里总结复习一下。

    栈对象:
      隐含调用构造函数(程序中没有显示调用)。
    代码如下:
    编译运行:
    再次看结果:
    堆对象:
      隐含调用构造函数(程序中没有显示调用)。
    编译运行:
    从结果可以发现堆中创建的对象,在出了作用域之后并未自动释放,也就是说对象的作用域跟生存期是不等同的
    下面手动释放它:
    编译运行:
    全局对象、静态全局对象:
    ①、全局对象的构造先于main函数。
    编译运行:
    编译运行:
    ②、已初始化的全局变量或静态全局对象存储于.data段中,未初始化的全局变量或静态全局对象存储于.bss段中。
    关于这个内容在之前的Linux系统编程中已经学习过了,这里回顾一下:
    静态局部对象:
    ①、已初始化的静态局部变量存储于.data段中,未初始化的静态局部变量存储于.bss段中。
    编译运行:
    从中可以发现对象t4出了作用域并未及时释放,而是出了整个程序之后才释放的。另外还要注意:初始化的静态变量是在编译期初始化的,而静态对象变量是在运行期才初始化的。

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

    如果函数执行完了,这时n的值还是100,第二次再进这个函数时,还是100,该函数被称为不可重录函数,也不是线程安全的函数,一般这种函数不会用在信号处理中用到,这个在Linux编程中有介绍过。

    ②、 用在文件级别(函数体之外),修饰变量或函数,表示该变量或函数只在本文件可见,其他文件看不到也访问不到该变量或函数。专业的说法叫“具有internal linkage”(简言之:不暴露给别的translation unit)。

    所以要定义全局变量,就应该在.c文件中定义,千万不能在.h头文件中定义:

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

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

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

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

    关于什么是单例模式这里就不多介绍了,在平常工作中会大量用到,比如你如果用过java,这里简单列一下它的定义:

    • 保证一个类只有一个实例,并提供一个全局访问点。
    • 禁止拷贝

     

    编译运行:

    上面就实现了一个单例模式,但是还是有一些缺陷的,下面来改一下代码:

    编译运行:

    这是第一个缺陷,紧接着还存在一个问题,如下:

    编译:

    那不让其拷贝很简单,如下:

    这时再编译:

    接着就要解决析构不能被调用的问题,进一步改造单例模式:

    #include <iostream>
    using namespace std;
    
    class Singleton {
    public:
        static Singleton* getInstance() {
            if(instance_ == NULL)//做一层判断,保证调用此方法永远返回同一个实例
                instance_ = new Singleton();
            return instance_;
        }
        ~Singleton() {
            cout<<"~Singleton ..."<<endl;
        }
    
        static void free() {
            if(instance_ != NULL) 
                delete instance_;
        }
    
    private:
        Singleton(const Singleton& other);
        Singleton& operator=(const Singleton& other);
        Singleton(){//将构造函数声明成私有的,防止外部进行实例化
            cout<<"Singleton ..."<<endl;
        }
        static Singleton* instance_;
    };
    
    Singleton* Singleton::instance_;
    
    int main(void) {
        Singleton* s1 = Singleton::getInstance();
        Singleton* s2 = Singleton::getInstance();
        //Singleton s3(*s1);            //调用拷贝构造函数,而会生成一个s1对象,目前这种写法是可以允许的,也就不是单例了
        //Singleton s3 = *s2;            //不允许调用=号运算符来赋值
        Singleton::free();      //在需要的时候手动调用释放代码
        return 0;
    }

    编译运行:

    成功解决,但是:如果一个程序有很多地方都使用了单例的对象,到底哪里进行手动释放我们也不太好确定,所以这种方法不是很可取,我们应该让它自动释放,在这个单例对象生命周期限结束之后自动释放,解决方案是:使用内嵌类来解决,具体代码如下:

    #include <iostream>
    using namespace std;
    
    class Singleton {
    public:
        static Singleton* getInstance() {
            if(instance_ == NULL)//做一层判断,保证调用此方法永远返回同一个实例
                instance_ = new Singleton();
            return instance_;
        }
        ~Singleton() {
            cout<<"~Singleton ..."<<endl;
        }
    
        static void free() {
            if(instance_ != NULL) 
                delete instance_;
        }
    
        class Garbo {
        public:
            ~Garbo() {
                if(Singleton::instance_ != NULL)
                    delete instance_;
            }
        };
    
    private:
        Singleton(const Singleton& other);
        Singleton& operator=(const Singleton& other);
        Singleton(){//将构造函数声明成私有的,防止外部进行实例化
            cout<<"Singleton ..."<<endl;
        }
        static Singleton* instance_;
        static Garbo garbo_;        //利用对象的确认性析构,当对象的生命周期结束之后会主动调用析构来达到目的
    };
    
    Singleton* Singleton::instance_;
    Singleton::Garbo Singleton::garbo_;
    
    int main(void) {
        Singleton* s1 = Singleton::getInstance();
        Singleton* s2 = Singleton::getInstance();
        //Singleton s3(*s1);            //调用拷贝构造函数,而会生成一个s1对象,目前这种写法是可以允许的,也就不是单例了
        //Singleton s3 = *s2;            //不允许调用=号运算符来赋值
        //Singleton::free();
        return 0;
    }

    编译运行:

    可以看到利用这种确认性析构自动调用的析构方法,其实单例还有另外一种实现方式,如下:

    利用静态对象的特征来实现单例模式,这是最简单的一种实现方式,编译运行:

    再次编译运行:

    但是这是线程不安全的单例模式,关于怎么实现线程安全的单例模式,在之后会学习到。

    ①、const成员函数不会修改对象的状态。

    ②、const成员函数只能访问数据成员的值,而不能修改它。

    如果在这个函数中对x_成员变量进行修改,则会报错:

     

     

    ①、如果把一个对象指定为const,就是告诉编译器不要修改它。

    ②、const对象的定义:const 类名 对象名(参数表);

    ③、const对象不能调用非const成员函数

    编译:

    编译运行:

     用mutable修饰的数据成员即使在const对象或在const成员函数中都可以被修改。

    如下面这个场景:

    #include <iostream>
    using namespace std;
    
    class Test {
    public:
        Test(int x):x_(x), outputTimes_(0) {
    
        }
        int getX() const{
            //x_ = 100;            ERROR,const成员函数不能修改成员
            cout<<"const getX..."<<endl;
            return x_;
        }
    
        int getX(){
            cout<<"getX..."<<endl;
            return x_;
        }
    
        void output() const {//在对象输出时,要对次数成员变量进行+1,以便统计对象输出的次数
            cout<<"x="<<x_<<endl;
            outputTimes_++;
        }
    
        int getOutPutTimes() const{//打印总输出次数
            return outputTimes_;
        }
    
    private:
        int x_;
        int outputTimes_;//统计对象被输出的次数
    };
    
    int main(void) {
        const Test t(10);
        t.getX();
        Test t2(20);
        t2.getX();
        return 0;
    }

    编译运行:

    那怎么解决这个冲突呢?当然就是用mutable关键字来解决喽,如下:

    再次编译:

    可见冲突解决了,下面编写测试代码:

    运行:

    最后再对const进行一个总结,已经学习过了很多相关的用法:

    第一种用法:定义常量

    比如:const int n = 100;

    const Test t(10);

    第二种用法:const引用

    比如:const int& ref = n,但是不能这样写:int& ref = n;

    第三种用法:const与指针

    放在*号左边,如下:

    const int* p;//const出现在*号左边,表示*p是常量,*p=200是错的。

    放在*号右边,如下:

    int * const p2;//const 出现在*号右边,表示p2是常量,p2=&n2则是错的。

    const int* const p3 = &n3;//*p3既是常量,p3也是常量。

    另外在类中,如果有const成员,const成员的初始化只能在构造函数初始化列表中进行。

    第四种用法:const成员函数【本节所学】

    表示成员函数不能修改对象状态,也就是只能访问数据成员,但是不能修改数据成员。

  • 相关阅读:
    Ruby系列:玩转闭包(Block,Proc,lambda)
    C# 中where关键字【MSDN】
    web应用程序中慎用static变量
    面向对象的javascript(一)
    原型模式 对象深浅复制
    存储过程:异地备份数据库文件
    正则表达式 [笔记]
    连接Oracle数据库代码
    在Eclipse下搭建Android开发环境教程,HelloWord
    Android开发之旅:环境搭建及HelloWorld
  • 原文地址:https://www.cnblogs.com/webor2006/p/5137911.html
Copyright © 2011-2022 走看看