一、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;
}
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
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的级别跟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关键字
#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修饰成员函数 表示该成员函数不能修改对象状态,也就是说它只能访问数据成员,但是不能够修改数据成员