对象的使用(一)

static 成员

  • 对于特定类型的全体对象而言,有时候可能需要访问一个全局的变量,比如说统计某种类型对象已创建的数量;
  • 如果我们用全局变量会破坏数据的封装,一般的用户代码都可以修改这个全局变量,这时候我们可以用类的静态成员来解决这个问题;
  • 非 static 数据成员存在于类类型的每个对象中,static 数据成员独立该类的任意对象存在,它是与类关联的对象,不与类对象关联。与所有的类对象是共享的
class CountedObject
{
public:
CountedObject();
~CountedObject();

static int count_; //静态成员的引用性声明,还需要有定义性的说明
};

int CountedObject::count_ = 100; // 静态成员的定义性声明,不需要 static 关键字 ,初始化为 100,默认初始化为 0
// 在 .c 文件中

static 成员的优点

  • static 成员的名字是在类的作用域中,因此可以避免与其他类成员或全局对象名字冲突;
  • 可以实施封装,static 成员可以是私有的,而全局对象不可以;如果 static 成员是私有的,意味着外部的代码不能访问该成员,需要提供公有的接口来访问。
  • 阅读顺序可以看出 static 成员与某个类相关联,这种可见性可以清晰地反映程序员的意图。
class CountedObject
{
public:
CountedObject();
~CountedObject();
public:
static int GetCount(); //公有的接口访问私有的 count_ 成员
private: // 私有的静态成员
static int count_; //静态成员的引用性声明,还需要有定义性的说明
};

int CountedObject::GetCount()
{
return count_;
}

  • static 成员的定义
    • static 成员需要在类定义体外进行初始化与定义
  • 特殊的整型 static const 成员
    • 整型 static const 成员可以在类定义体中初始化,
    • 注意不能够多次初始化

static 成员函数

  • static 成员函数没有 this 指针
  • 非静态成员函数可以访问静态成员
  • 静态成员函数不可以访问非静态成员,静态成员函数不能调用非静态成员函数
  • 静态成员函数不能访问非静态成员,因为静态成员函数没有 this 指针
  • 访问静态成员函数可以 Test::x_ 来访问,推荐这种方式,除此之外,还可以通过点的方式来访问t.x_,不推荐

类/对象大小计算

  • 类大小计算遵循前面学过的结构体对齐原则
  • 类的大小与数据成员有关,与成员函数无关
  • 类的大小与静态数据成员无关
  • 虚函数对类的大小的影响
  • 虚继承对类的大小的影响

对象的使用(二)

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(void)
{
// 若不需要创建日期对象,仅仅是想判定日期是否是闰年,此时可以提供一个静态成员函数,静态函数类似 python 中的类函数
// 不需要创建对象,就能够访问
Date d(2012);
cout << d.IsLeapYear() << endl;

cout << Date::IsLeapYear(2010) << endl;
return 0;
}

四种对象作用域与生存期

  • 栈对象
    • 隐含调用构造函数(程序中没有显示调用)
  • 堆对象
    • 隐含调用构造函数(程序中没有显示调用)
  • 全局对象、静态全局对象
    • 全局对象的构造先于 main 函数
    • 已初始化的全局变量或静态全局对象存储于 .data 段中
    • 未初始化的全局变量或静态全局对象存储于 .bss 段中
  • 静态局部对象
    • 已初始化的静态局部变量存储于 .data 段中
    • 未初始化的静态局部变量存储于 .bss 段中
  • 作用域与生存期不总是等同的
Test t;  // 栈上创建对象,在生存期结束的时候能够自动释放
Test *t3 = new Test; // 堆上创建的对象,要显示释放,即使跳出了作用域能需要显示释放 delete
delete t3;
#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 symbol)
int n2 = 101; // 已初始化的全局变量,初始值为 101,n2 存储于 .data 段中
Test g(100); // 全局对象的构造先于 main 函数
static Test g2(200);
int main(void)
{
cout << "Enetring main..." << endl;
Test t(10); // 栈上创建对象,在生存期结束的时候能够自动释放
{
Test t(20);
}
{
Test *t3 = new Test(30); // 堆上创建的对象,要显示释放,即使跳出了作用域能需要显示释放 delete
delete t3;
}

{
static int n3; //n3 存储于 .bss 段中 (编译期初始化)
static int n4 = 102; //n4 存储于 .data 段中 (编译期初始化)
static Test t4(333); // t4 类对象运行期初始化 .data 段中
}
cout << "Exiting main..." << endl;
return 0;
}

/* 输出结果:
Test...100
Test...200
Enetring main...
Test...10
Test...20
~Test...20
Test...30
~Test...30
Test...333
Exiting main...
~Test...10
~Test...333
~Test...200
~Test...100
* /

static 用法总结

  1. 用于函数内部修饰变量,即函数内的静态变量,这种变量的生存期长于该函数,使得函数具有一定的“状态”,使用静态变量的函数一般是不可以重入的,也不是线程安全的,比如 strtok(3)
  2. 用在文件级别(函数体之外),修饰变量或函数,表示该变量或函数只在本文件可见,其他文件看不到也访问不到该变量或函数。专业的说法叫“具有 internal linkage” (简言之,不暴露给别的 translation unit)。C 语言的这两种用法很明确,一般也不容易混淆。由于 C++ 引入了类,在保持与 C 语言兼容的同时,static 关键字又有了两种新用法:
  3. 用于修饰类的数据成员,即所谓的“静态成员”。这种数据成员的生存期大于 class 的对象(实例/instance)。静态数据成员是每个 class 有一份,普通数据成员是每个 instance 有一份。
  4. 用于修饰 class 的成员函数,即所谓的“静态成员函数”。这种成员函数只能访问静态成员和其他静态成员函数,不能访问非静态成员和非静态成员函数(没有 this 指针)。

对象的使用(三)

static 与单例模式

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

// 此种情况下,析构函数不会被调用,需要进一步改造
#include <iostream>
using namespace std;

class Singleton{
public:
static Singleton* GetInstance()
{
if (instance_ == NULL) {
instance_ = new Singleton;
}
return instance_;
}

private: //只需要把构造函数声明成私有的,单例模式
Singleton(const Singleton& other); // 拷贝构造函数声明为私有的,可以禁止拷贝
Singleton& operator =(const Singleton& other); // 赋值操作也声明私有的,禁止拷贝
Singleton()
{
cout << "Singleton..." << endl;

}
~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); // 调用拷贝函数,要禁止拷贝


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_; //当garbo_ 销毁时,会自动调用 Garbo 的析构函数,此时会释放掉 instance_
// 利用了对象的确定性析构

};

Singleton::Garbo Singleton::garbo_;
Singleton* Singleton::instance_;
// 当程序结束的时候, garbo_ 的生存期结束,会调用该类的析构函数,析构函数里面会销毁 instance_,达到了销毁单例模式的对象目的
int main(void)
{
// Singleton s1;
// Singleton s2;
Singleton* s1 = Singleton::GetInstance();
Singleton* s2 = Singleton::GetInstance(); // 不管调用几次,返回的都是同一个对象,实例
// 会发现没有调用析构函数,没有释放资源,会发生资源泄露的问题
// Singleton s3(*s1); // 调用拷贝函数,要禁止拷贝
// Singleton::Free(); // 对资源进行释放

return 0;
}

// 简单的实现单例模式,但是不是线程安全的
#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 成员函数不会修改对象的状态
  • const 成员函数只能访问数据成员的值,而不能修改它
#include <iostream>
using namespace std;

class Test
{
public:
Test(int x) : x_(x) {}
int GetX() const
{
cout << "const GetX..." << endl;
// x_ = 100; // 错误的
return x_;
}

int GetX() // const 成员函数和非 const 成员函数可以构成重载
{
cout << "GetX..." << endl;
return x_;
}
private:
int x_;
};
int main(int argc, char *argv[])
{
Test t(10);
t.GetX();
cout << "Hello World!" << endl;
return 0;
}

const 对象

  • 如果把一个对象指定为 const,就是告诉编译器不要修改它
  • const 对象的定义:const 类名 对象名(参数表)
  • const 对象不能调用非 const 成员函数,只能够调用 const 成员函数
const Test t(10);  //对象是常量,状态不能被更改
t.GetX(); //不能调用 Getx() 函数, const 对象不能调用非 const 成员函数,非 const 成员函数有修改对象的潜在风险,即使没有更改
// 只能调用 const 成员函数

mutable

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

const 用法总结

  • const int n= 100; // 定义常量,必须初始化;
  • const Test t(10); // 定义常量对象,必须初始化;
  • const int& ref = n; // const 引用可以引用 const 常量,普通引用不能引用 const 常量;int& ref = n; //error
  • const 与指针
    • const int* p; // const 出现在*左边,表示 *p是常量,const 修饰的是 p指针指向的对象(*p = 200; error);
    • int* const p2; // const 出现在*右边,表示 p2 是常量,(p2 = &n; error);
    • const int* const p3 = &n3 // *p3是常量,p3也是常量;
  • 在类中,如果有 const 成员,const 成员的初始化只能在构造函数初始化列表中进行;
  • const 修饰成员函数,表示该成员函数不能修改对象状态,也就是它只能访问数据成员,但是不能修改数据成员。

一个实例看数据抽象与封装

/*
* 用 C++ 语言的方式实现栈操作
*/
#include <iostream>
using namespace std;
class Stack
{
struct Link
{
int data_;
Link* next_;
Link(int data, Link* next) : data_(data), next_(next) {}
};
public:
Stack() : head_(0), size_(0) {}
~Stack()
{
Link* tmp;
while (head_) {
tmp = head_;
head_ = head_->next_;
delete tmp;
}
}

void Push(const int data)
{
Link* node = new Link(data, head_);
head_ = node;
++size_;
}
bool Empty()
{
return (size_ == 0);
}

bool Pop(int& data)
{
if(Empty()) {
return false;
}
Link* tmp = head_;
data = head_->data_;
head_ = head_->next_;
delete tmp;
--size_;
}

private:
Link* head_;
int size_;

};
// 避免名称冲突
// 类型的扩充
// 数据封装,内部私有成员外部不能操作,能够保护内部的数据结构不遭受外界破坏
int main(void)
{
Stack stack; // 抽象数据类型 类类型
int i;
for (i = 0; i < 6; i++) {
stack.Push(i);
}

while (!stack.Empty()) {
stack.Pop(i);
cout << i << " ";
}
return 0;
}