对象的使用

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

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

③、非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成员函数【本节所学】

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

原文地址:https://www.cnblogs.com/webor2006/p/5137911.html