对象的使用

一、static成员、static成员函数、类/对象大小计算

static:

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

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

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

注:是被所有对象共享的


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

非私有静态成员的访问:CountedObject::count_

如果设置为私有则不能采用上述方法访问,可以写一个静态函数来进行访问,清除的看出程序员的意图

特殊的静态常量整型static const成员(char int)可以在类定义体中初始化,该成员可以不在类体外进行定义

(注:vc6是不支持这个语法的)

总结:静态成员需要在类体内引用性声明,还有在类体外进行定义性声明,只能初始化一次,不能多次初始化;一定要有定义性声明

CountedObject.h

#ifndef _COUNTED_OBJECT_
#define _COUNTED_OBJECT_
 
class CountedObject
{
public:
    CountedObject(void);
    ~CountedObject(void);
    static int count_;  //仅仅只是一个声明,引用性声明   还必须在文件作用域内进行说明
 
//public:
//  static int GetCount();
//private:
//  static int count_;
 
};
#endif
CountedObject.cpp

#include "CountedObject.h"
 
//不能在类声明时候给出,成员变量都是如此
int CountedObject::count_ /*= 100*/;//静态成员的定义性说明
 
//因为在类作用域 所以没必要+上类运算符 可以将其忽略
CountedObject::CountedObject(void)
{
    ++count_;
}
CountedObject::~CountedObject(void)
{
    --count_;
}
main.cpp

#include"CountedObject.h"
#include<iostream>
using namespace std;
 
int main(void)
{
    cout<<CountedObject::count_<<endl;
    CountedObject co1;
    cout<<CountedObject::count_<<endl;
    CountedObject* co2 = new CountedObject;
    cout<<CountedObject::count_<<endl;
    delete co2;
    cout<<CountedObject::count_<<endl;
}

static成员函数没有this指针

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

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

总结:静态成员函数不能调用非静态成员函数,因为没有this指针,所以也不能访问非静态成员

//而普通的函数可以访问

3、同理:静态成员函数不能调用非静态成员函数,

class Test
{
public:
    Test(int y):y_(y)
    {
    }
    ~Test()
    {
    }
    //静态成员的引用性说明  char类型也是可以的  也不能够多次初始化
    //vc6.0不支持static const
     
    //非静态成员函数可以访问静态成员
    //静态成员函数不可以访问非静态成员
    void TestFun()
    {
        cout<<"x_ = "<<x_<<endl;
        TestStaticFun();
    }
    static void TestStaticFun()
    {
        cout<<"TestStaticFun..."<<endl;
        //TestFun(); 静态成员函数不能调用非静态成员函数  因为没有this指针的存在
        //cout<<"y = "<<y_<<endl;  Error,静态成员函数不能访问非静态成员,因为没有
                            //默认的this指针,非静态成员不是共享的,
    }
    static int x_;
    int y_;
    //Error   static const double = 100;  不允许非整形进行引用性说明
 
};
//还是必不可少的!
 int Test::x_;   //静态成员的定义性说明
int main(void)
{
    Test t(10);
    cout<<Test::x_<<endl;
    t.TestFun();
 
    //以下这种方式不方便使用   容易让人误认为是类中的对象  而不是所有共享
    cout<<t.x_<<endl;
    cout<<sizeof(t)<<endl;
}

类大小的计算:

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

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

3、类的大小与静态数据成员无关(因为静态成员是共享的,不是没有对象都有的,所以与之无关)

4、虚函数对类的大小的影响    对增加4个字节的空间  指向虚表指针

5、虚继承对类的大小的影响



二、四种对象的作用域与生存期、static用法总结

回顾下static用法的好处:

#include <iostream>
using namespace std;
 
class Date
{
public:
    Date(int year):year_(year)
    {
    }
    //这样子就不用访问成员变量
    static bool IsLeapYear(int year_)
    {
        return (year_ % 4 == 0 && year_%100 != 0) ||(year_ % 400 == 0);
    }
    bool IsLeapYear()
    {
        return (year_ % 4 == 0 && year_%100 != 0) ||(year_ % 400 == 0);
    }
private:
    int year_;
};
int main()
{
    //仅仅只需要判定一个日期是不是闰年  不需要创建日期对象
    //仅仅需要提供一个静态成员函数
 
    Date d(2012);
    //还要提供一个对象  才可以使用静态成员函数 这样子太不自然
    cout<<d.IsLeapYear(2010)<<endl;
 
    //自然写法  不需要构造对象
    cout<<Date::IsLeapYear(2010)<<endl;
    return 0;
}
作用域与生存期不总是等同的!!!

比如说堆上的对象,作用域只是指可见的范围,比如说只能在当前块,但是出了块后,在堆上还有记录,仍然生存

#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_;
};
 
//全局对象的构造先于main函数
Test g(10);
static Test g2(200);//全局对象都会先于main函数
int main(void)
{
    Test t(10);//栈对象  能够自动释放   生存期结束后会自动调用析构函数
    {
        Test t(20);
    }
    {
    Test *t3 = new Test(30);
    delete t3;//堆上创建的对象 要显示释放
    }
    cout<<"Exiting main..."<<endl;
}

一、栈对象

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

二、堆对象

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

三、全局对象、静态全局对象

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

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

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

四、静态局部对象

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

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



代码段也包括常量

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 symbot
int n2 = 100;//未初始化的全局变量初始值为100   n2存储于DATA段中
//全局对象的构造先于main函数
Test g(10);
static Test g2(200);//全局对象都会先于main函数
 
int main(void)
{
    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;
}


static用法总结:

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

如:

void foo()

{
static int n = 100;

n = 200;

}//该函数不应该用在信号处理函数中

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

a.c:

static int n = 100;//表示只能在本文件内可见

b.c

static int n = 100;//只在本文件内可见   

所以有两个不同的n


而如果a.c和b.c想用同一个变量的话

int n = 100;   全局变量  在.c文件中定义

extern int n;   称为external linkage


而如果在x.h中定义

int n = 100;

在a.c   int n = 100;

b.c      int n = 200;//重复定义

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

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


三、static与单例模式

单例模式是设计模式中的一种,是比较简单的,保证一个类只有一个实例,并提供一个全局访问点,禁止拷贝

#include<iostream>
using namespace std;
 
class Singleton
{
public:
	//提供一个全局的访问点,静态成员函数
	//获取实例
    static Singleton* GetInstance()
    {
        if(instance_ == NULL)
        {
            instance_ = new Singleton;
        }
        return instance_;
    }
private:
	//设置为私有 这样子就没法得到此类对象
    Singleton()
    {
    }
    //仅仅只是一个声明
	//由所有对象共享,所以只有一个实例
    static Singleton* instance_;
 
};
 
Singleton *Singleton::instance_;
 
int main(void)
{
    //将构造函数设计为私有的  则无法访问
    //Singleton s1;
    //Singleton s2;
 
    //返回的总是同一个实例
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();
 
    return 0;
}
按shift+F11可以跳出函数
可以通过查看构造函数构造了几次来验证

上面的例子对象并没有删除,这就存在着资源泄漏的问题

而且也没有实现禁止拷贝  先解决拷贝问题

禁止拷贝:

#include<iostream>
using namespace std;
 
class Singleton
{
public:
    static Singleton* GetInstance()
    {
        if(instance_ == NULL)
        {
            instance_ = new Singleton;
        }
        return instance_;
    }
 
    ~Singleton()
    {
        cout<<"~SIngleton..."<<endl;
    }
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 s2;
 
    //返回的总是同一个实例
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();
 
    //通过定义为私有  就能让其错误
    //Singleton s3(*s1);    //是不能拷贝的  所以要禁止拷贝,要让此操作是不合法的
    /*s3 = *s2;*/
 
 
    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_;
 
};
//要使用定义性的声明  可以不修饰static
Singleton::Garbo Singleton::garbo_;
Singleton *Singleton::instance_;
 
int main(void)
{
    //将构造函数设计为私有的  则无法访问
    //Singleton s1;
    //Singleton s2;
 
    //返回的总是同一个实例
    Singleton *s1 = Singleton::GetInstance();
    Singleton *s2 = Singleton::GetInstance();
 
    //通过定义为私有  就能让其错误
    //Singleton s3(*s1);    //是不能拷贝的  所以要禁止拷贝,要让此操作是不合法的
    /*s3 = *s2;*/
 
    //这样子调用了析构函数  但是在哪里释放不好控制,实际上比较大的工程是不能这么做的,要让
    //它有自动释放的功能
    //Singleton::Free();
    return 0;
}
上述还是不够完美,使用auto_ptr智能指针,后面进行实现
再使用另一种实现方案:

#include<iostream>
using namespace std;
 
class Singleton
{
public:
	//返回必须使用引用,不然会调用拷贝构造函数
    static Singleton& GetInstance()
    {
        static Singleton instance;//局部静态对象 运行期初始化有状态
        return instance;
    }
 
    ~Singleton()
    {
        cout<<"~SIngleton..."<<endl;
    }
 
private:
    //拷贝私有
    Singleton(const Singleton& other);
    //赋值私有
    Singleton& operator=(const Singleton& other);
    Singleton()
    {
        cout<<"Singleton..."<<endl;
    }
 
};
 
 
int main(void)
{
    //不能少引用 因为会调用拷贝构造函数
    Singleton& s1 = Singleton::GetInstance();
    Singleton& s2 = Singleton::GetInstance();
    return 0;
}

线程安全的单例模式类的实现在智能指针中会涉及,并设计为模版

四、const成员函数、const对象、mutable

const成员函数:

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

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

3、可以构成重载

#include <iostream>
using namespace std;

class Test
{
public:
	Test(int x):x_(x)
	{
	}
	//不会更改x_数据成员的值,所以用const修饰
	int GetX() const
	{
		//x_ = 100  error
		return x_;
	}
	//可以构成重载
	int GetX()
	{
		cout<<"GetX.."<<endl;
		return x_;
	}
private:
	int x_;
};
int main()
{
	return 0;
}


const对象:

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

const对象的定义:

const 类名 对象名(参数表);

const对象不能调用非const成员函数//const的级别跟static差不多

#include<iostream>
using namespace std;
class Test
{
public:
    Test(int x):x_(x)
    {
    }
    //const成员函数与非const成员函数可以构成重载
    //因为不会修改对象成员的值  所以采用const成员函数
    int GetX() const
    {
        //x_ = 100;//这是不允许的   左值不能指定
        return x_;
    }
    //int GetX()
    //{
    //  return x_;
    //}
private:
    int x_;
};
 
int main(void)
{
    const Test t(10);//意味着const对象的状态是不能被更改的
    //const对象不能调用非const成员函数,因为存在对对象修改潜在的危险
    t.GetX();
    Test t1(10);
    t.GetX();
    return 0;
}


假设想统计某个对象输出的次数;定义一个一个数据成员outputTimes_;

void Output() const

{
cout<<"x = "<<x_<<endl;

outputTimes_++

}

产生了矛盾,Output函数不希望有改变,但是outputTimes_又希望是可变的

怎么办?解决方案:mutable关键字


用mutable(易变的  与const是相对的)修饰的数据成员即使在const对象或在const成员函数中都可以被修改
#include<iostream>
using namespace std;
class Test
{
public:
    Test(int x):x_(x),outputTimes_(0)
    {
    }
    //const成员函数与非const成员函数可以构成重载
    //因为不会修改对象成员的值  所以采用const成员函数
    int GetX() const
    {
        //x_ = 100;//这是不允许的   左值不能指定
        return x_;
    }
    int GetX()
    {
        return x_;
    }
 
    void Output() const
    {
        cout<<"x="<<x_<<endl;
        //是不能修改数据成员的值  但是又希望修改它  所以要用到mutable
        outputTimes_++;  
 
    }
 
    int GetOutPutTimes() const
    {
        return outputTimes_;
    }
private:
    int x_;
    mutable int outputTimes_;
};
 
int main(void)
{
    const Test t(10);//意味着const对象的状态是不能被更改的
    //const对象不能调用非const成员函数,因为存在对对象修改潜在的危险
    t.GetX();
    Test t1(10);
    t.GetX();
    t.Output();
    t.Output();
    cout<<t.GetOutPutTimes()<<endl;
    return 0;
}

const用法总结:

1、const int n = 100;//定义常量

2、const Test t(10);

3、const int &ref = n;//const引用

int &ref = n  ;//error

4、const与指针

const int *p;//表示*p是常量 (*p) = 200; Error

int *const p2;//表示p2是常量  p2不能再指向其他对象  p2 = &n2;错误

const int *const p3 = &n3;

在类中

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

const修饰成员函数  表示该成员函数不能修改对象状态,也就是说它只能访问数据成员,但是不能够修改数据成员















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值