第七章 类和对象 Part3 类的静态成员

静态成员

  • 在类定义中,它的成员(包括成员变量和成员函数),这些成员可以用关键字static声明为静态的,称为静态成员。
  • 不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的对象共享。

静态成员变量

1.在一个类中,若将一个成员变量声明为static,这种成员称为静态成员变量。与一般的数据成员不同,无论建立了多少个对象,都只有一个静态数据的拷贝。静态成员变量,属于某个类,所有对象共享。
2.静态变量,是在编译阶段就分配内存空间,放在全局静态区;在对象还没有创建时,就已经分配空间。
    1. 静态成员变量必须在类中声明,在类外定义。
    1. 静态数据成员不属于某个对象,在为对象分配空间中不包括静态成员所占空间。
    1. 静态数据成员可以通过类名或者对象名来引用。
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
//#include<string>
using namespace std;

class Person
{
public:
	static int m_Age;//加入static就是静态变量了,会共享数据
	//静态成员变量,在类内声明,在类外进行初始化
private:
	static int m_other;//私有权限,在类外不能访问,但是在类外可以初始化
};
int Person::m_Age = 10;//类外初始化实现
int Person::m_other = 10;//看似实在类外进行的初始化,其实写上作用域之后,也算是写到类内了

void test01()
{
	//1.通过对象访问属性
	Person p1;
	p1.m_Age = 10;

	Person p2;
	p2.m_Age = 20;

	cout << "p1 = " << p1.m_Age << endl;//10 还是 20? 20 //原因:共享数据
	cout << "p2 = " << p2.m_Age << endl;//10 还是 20? 20 //原因:共享数据 

	//2.通过类名访问属性
	cout << "通过类名访问Age:" << Person::m_Age << endl;
	//这就是为什么不要在类内实现:
	//因为,如果通过对象去访问这个属性,可以调用构造函数
	//但是如果通过类名,就不能了调用构造函数了,即不能通过类名访问这个属性了
}

int main()
{
	test01();

	system("pause");
	return 0;
}

image

静态成员函数

  1. 在类定义中,前面有static说明的成员函数称为静态成员函数。静态成员函数使用方式和静态变量一样,同样在对象没有创建前,即可通过类名调用。
  2. 静态成员函数主要为了访问静态变量,但是,不能访问普通成员变量。
  3. 静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。
  • 静态成员函数只能访问静态变量,不能访问普通成员变量
  • 静态成员函数的使用和静态成员变量一样
  • 静态成员函数也有访问权限
  • 普通成员函数可访问静态成员变量、也可以访问非静态成员变量
点击查看代码
class Person{
public:
	//普通成员函数可以访问static和non-static成员属性
	void changeParam1(int param){
		mParam = param;
		sNum = param;
	}
	//静态成员函数只能访问static成员属性
	static void changeParam2(int param){
		//mParam = param; //无法访问
		sNum = param;
	}
private:
	static void changeParam3(int param){
		//mParam = param; //无法访问
		sNum = param;
	}
public:
	int mParam;
	static int sNum;
};

//静态成员属性类外初始化
int Person::sNum = 0;

int main(){

	//1. 类名直接调用
	Person::changeParam2(100);

	//2. 通过对象调用
	Person p;
	p.changeParam2(200);

	//3. 静态成员函数也有访问权限
	//Person::changeParam3(100); //类外无法访问私有静态成员函数
	//Person p1;
	//p1.changeParam3(200);
	return EXIT_SUCCESS;
}

const静态成员属性

  1. 如果一个类的成员,既要实现共享,又要实现不可改变,那就用 static const 修饰。
  2. 定义静态const数据成员时,最好在类内部初始化。
class Person{
public:
	//static const int mShare = 10;
	const static int mShare = 10; //只读区
};
int main(){

	cout << Person::mShare << endl;
	//Person::mShare = 20;

	return EXIT_SUCCESS;
}

单例设计模式

  • 单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。
  • 通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
  • 如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
    image
    Singleton(单例):
  • 在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;
  • 为了防止在外部对其实例化,将其默认构造函数和拷贝构造函数设计为私有;
  • 在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。
单例模式-ChairMan案例
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
//#include<string>
using namespace std;

//创建主席类
//需求 单例模式 是为了创建类中的对象,并且保证只有一个对象实例
class ChairMan
{
//提供get方法来访问主席
public:
	static ChairMan * getInstance()//前面要加一个static,不然只能通过对象访问,而不能通过类名直接访问
	{
		return singleMan;
	}

	//单例模式
	//1.构造函数私有化
private:
	ChairMan()
	{
		cout << "创建主席构造" << endl;
	}

	//拷贝构造函数私有化
private:
	ChairMan(const ChairMan & cm)
	{
		cout << "调用ChairMan的拷贝构造函数" << endl;
	}

//public:
private:
	static ChairMan * singleMan;//类内声明一个singleMan的指针,

};

ChairMan * ChairMan::singleMan = new ChairMan;//类外初始化,让这个指针指向new的这个ChairMan的内存

void test01()
{
	//ChairMan c1;
	//ChairMan * c2 = new ChairMan;
	//ChairMan * c3 = new ChairMan;
	//私有化之后不能再通过new的方式创建对象了


	//对象的创建编译阶段就已经完成了,
	//因为编译阶段会加载静态变量
	ChairMan * cm1 = ChairMan::getInstance();//这俩是同一个
	ChairMan * cm2 = ChairMan::getInstance();//这俩是同一个

	//ChairMan::singleMan = NULL;//现在便便一个置空就能销毁这个对象
	//此时,将singleMan也给private
	//但是私有化之后,通过类调用就访问不到了
	//此时,可以通过get()来访问
	//ChairMan::singleMan = NULL;//此时就不能再通过置空来销毁对象了
	//因为对象指针为private,只有一个get的方法,没有set方法
	if (cm1 == cm2)
	{
		cout << "cm1和cm2指向同一个对象" << endl;
	}
	else
	{
		cout << "cm1和cm2指向不同对象" << endl;
	}

	//
	//ChairMan * cm3 = new ChairMan(*cm2);//cm2是一个指针,*cm2是它的本体,通过cm1本体创建出一个拷贝构造

	//判断一个cm3和cm2是否指向同一个对象
	//结果:不相同,即通过拷贝构造,又创建了一个主体
	//此时,就要把拷贝构造函数也私有化
	//if (cm3 == cm2)
	//{
	//	cout << "cm3和cm2指向同一个对象" << endl;
	//}
	//else
	//{
	//	cout << "cm3和cm2指向不同对象" << endl;
	//}

	//此时也不能再通过拷贝构造创建对象了
	//ChairMan * cm3 = new ChairMan(*cm2);

}

int main()
{
	cout << "main调用" << endl;//主席先于main调用
	test01();
	

	system("pause");
	return 0;
}
单例模式-打印机案例
//用单例模式,模拟公司员工使用打印机场景,打印机可以打印员工要输出的内容,并且可以累积打印机使用次数。`
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

//单例模式的案例
class Printer
{
private:
	Printer(){}
	Printer(const Printer &p){}

private:
	static Printer * singlePrinter;
	int mCount = 0;//要初始化
public:
	static Printer * getInstance()
	{
		return singlePrinter;//返回一个指针
	}

	void printText(string text)
	{
		cout << text << endl;
		mCount++;
		cout << "Printer 使用了:" << mCount << "次" << endl;
	}
};

Printer * Printer::singlePrinter = new Printer;


void test01()
{
	//拿到打印机
	Printer * printer = Printer::getInstance();
	printer->printText("best wish to you");
	printer->printText("best wish to me");
	printer->printText("best wish to him");
}

int main()
{

	test01();
	

	system("pause");
	return 0;
}

类中成员的存储方式

结论:

  1. 成员变量和成员函数是分开存储的,
  2. 空类的大小是 1
  3. 只有非静态成员变量存放在对象身上
    1. 在c语言中,变量和函数“分开来声明的,也就是说,语言本身并没有支持“数据”和“函数”之间的关联性,

    2. 我们把这种程序方法称为“程序性的”,由一组“分布在各个以功能为导航的函数中”的算法驱动,它们处理的是共同的外部数据。

    3. c++实现了“封装”,那么数据(成员属性)和操作(成员函数)是什么样的呢?
      C++编译器看上去,像是把成员变量和成员函数放在一起了,但真正的存储方式上它们依然是分开的。

“数据”和“处理数据的操作(函数)”是分开存储的。

  • c++中的非静态数据成员直接内含在类对象中,就像c struct一样。
  • 成员函数(member function)虽然内含在class声明之内,却不出现在对象中。
  • 每一个非内联成员函数(non-inline member function)只会诞生一份函数实例.
    image
点击查看代码
class Person
{
	int m_A;//1.非静态的成员变量,属于对象身上
	void func()	{}//2.非静态成员函数,不属于对象身上  	//函数名是函数的指针
	static int m_B;//3.静态成员变量,不属于对象身上
	static void func1();//4.静态成员函数,不属于对象身上

	//结论:
		//只有非静态成员函数,才属于对象身上

	double m_C;//
};

void test01()
{
	cout << "size of (Person) = " << sizeof(Person) << endl;
	//空类的大小为 1,空类也是可以创建实例对象的,每一个实例对象都有独一无二地址
	//为了防止实例对象冲突,类内部有一个char来维护这个地址
	//Person[10]; 
	//虽然数组是空的,但是要区分p[0]、p[1]...,每一个位置都是不一样的,用char来区分
}

image

由于字节对齐,int按照最大的double去算了,因此是16字节。

怎么改?
在代码头加入#pragma pack(1)//一个一个来就行了

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
#pragma pack(1)
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
#pragma pack(1)


class Person
{
	int m_A;//1.非静态的成员变量,属于对象身上
	void func()	{}//2.非静态成员函数,不属于对象身上  	//函数名是函数的指针
	static int m_B;//3.静态成员变量,不属于对象身上
	static void func1();//4.静态成员函数,不属于对象身上

	//结论:
		//只有非静态成员函数,才属于对象身上

	double m_C;//
};

void test01()
{
	cout << "size of (Person) = " << sizeof(Person) << endl;
	//空类的大小为 1,空类也是可以创建实例对象的,每一个实例对象都有独一无二地址
	//为了防止实例对象冲突,类内部有一个char来维护这个地址
	//Person[10]; 
	//虽然数组是空的,但是要区分p[0]、p[1]...,每一个位置都是不一样的,用char来区分
}

int main()
{
	test01();
	
	system("pause");
	return 0;
}

this指针的工作原理

通过上例我们知道,c++的数据和操作也是分开存储,并且每一个非内联成员函数(non-inline member function)只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?

image

this指针的使用

当形参和成员变量同名时,可用this指针来区分
在类的非静态成员函数中返回对象本身,可使用return *this.

c++通过提供特殊的对象指针,this指针,解决上述问题。This指针指向被调用的成员函数所属的对象。
  c++规定,this指针是隐含在对象成员函数内的一种指针。当一个对象被创建后,它的每一个成员函数都含有一个系统自动生成的隐含指针this,用以保存这个对象的地址,也就是说虽然我们没有写上this指针,编译器在编译的时候也是会加上的。因此this也称为“指向本对象的指针”,this指针并不是对象的一部分,不会影响sizeof(对象)的结果。
this指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数连接在一起,在外部看来,每一个对象都拥有自己的函数成员。一般情况下,并不写this,而是让系统进行默认设置。
this指针永远指向当前对象。//有一个const在修饰

成员函数通过this指针即可知道操作的是那个对象的数据。This指针是一种隐含指针,它隐含于每个类的非静态成员函数中。This指针无需定义,直接使用即可。
注意:静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量。

静态成员函数不能操作非静态成员变量,为什么?因为静态成员函数是在编译时就执行的,而非静态成员变量是在运行时执行的

image

this指针的使用

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this.
点击查看代码
class Person{
public:
	//1. this可以解决命名冲突
     //当形参名和成员变量名一样时,this指针可用来区分
     //不区分就报错
	Person(string name,int age){
		//name = name;
		//age = age; //输出错误
		this->name = name;
		this->age = age;
	}

	//2. 返回对象本身的引用
	//重载赋值操作符
	//其实也是两个参数,其中隐藏了一个this指针
	Person PersonPlusPerson(Person& person){
		string newname = this->name + person.name;
		int newage = this->age + person.age;
		Person newperson(newname, newage);
		return newperson;
	}
	void ShowPerson(){
		cout << "Name:" << name << " Age:" << age << endl;
	}
public:
	string name;
	int age;
};

//3. 成员函数和全局函数(Perosn对象相加)
Person PersonPlusPerson(Person& p1,Person& p2){
	string newname = p1.name + p2.name;
	int newage = p1.age + p2.age;
	Person newperson(newname,newage);
	return newperson;
}

int main(){

	Person person("John",100);
	person.ShowPerson();

	cout << "---------" << endl;
	Person person1("John",20);
	Person person2("001", 10);
	//1.全局函数实现两个对象相加
	Person person3 = PersonPlusPerson(person1, person2);
	person1.ShowPerson();
	person2.ShowPerson();
	person3.ShowPerson();
	//2. 成员函数实现两个对象相加
	Person person4 = person1.PersonPlusPerson(person2);//需求不同
	person4.ShowPerson();

	system("pause");
	return EXIT_SUCCESS;
}
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;

class Person
{
public:
	Person(int age)
	{
		age = age;
	}
	int age;
};

void test01()
{
	Person p1(10);
}


int main()
{
	test01();

	system("pause");
	return 0;
}

image

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;


//this 可以解决命名冲突

class Person
{
public:
	Person(int age)
	{
		this->age = age;
	}

	//对比年龄
	void compareAge(Person &p)
	{
		if (this->age < p.age)//这里不写this,还是会区分的,因为默认加了一个this
		{
			cout << "小了,格局小了" << endl;
		}
		else if(this->age > p.age)
		{
			cout << "果然,英明神武小郎君,世上再无我这般人" << endl;
		}
		else
		{
			cout << "道相同,可与谋" << endl;
		}

	}

	//年龄相加
	Person & PlusAge(Person& p)
	{
		this->age += p.age;
		return *this;//this是一个指针,*this是this指向的对象的本体
	}

	int age;

};

void test01()
{
	Person p1(10);//有this指针之后,当前用Person的构造方法的时候会默认加一个 Person * this 这个指针进去
	cout << "p1的年龄为:" << p1.age << endl;

	Person p2(10);
	p2.compareAge(p1);

	p1.PlusAge(p2);
	cout << "p1的年龄为:" << p1.age << endl;//

	//PLusAge()返回的是void,如果想继续调用,就要把对象p1再返回来;
 //而再返回引用的时候,函数可以作为左值,因此要把返回值改为Person &(Person的引用)
	//如果不返回引用,如下

	//Person PlusAge(Person & p)
	//{
	//	this->age += p.age;
	//	return *this;//this是一个指针,*this是this指向的对象的本体
	//}

//虽然 return *this,虽然看着像是返回自身; 但是返回值的数据类型是Person,是以值的方式来返回的.
//之前学的拷贝构造函数的调用时机:当返回值以值的方式返回的时候,会调用拷贝构造,这是就会按照本体拷贝出一个新的数据,然后将新的数据返回
//因此第一个p1.PlusAge(p2)确实把p2加上去了,但是它返回的是临时的看不到的新数据,
	//这时再.PlusAge(p2).PlusAge(p2) ,调用的对象已经不再是p1了,再.PlusAge(p2)也不是往p1身上加了
 //因此,如果是想做链式编程,想往自身加的话,必须返回的是引用。


	p1.PlusAge(p2).PlusAge(p2).PlusAge(p2);//链式编程
	cout << "p1的年龄为:" << p1.age << endl;//
}


int main()
{
	test01();

	system("pause");
	return 0;
}

image

空指针访问成员函数

  1. 如果成员函数没有用到指针,那么空指针可以直接访问
  2. 如果成员函数用到this指针,就要注意,如果this为空,就要return掉
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;


//this 可以解决命名冲突

class Person
{
public:
	void show()
	{
		cout << "Person show" << endl;
	}

	void showAge()
	{
		cout << m_Age << endl;
	}

	int m_Age;
};

void test01()
{
	Person* p = NULL;//空指针
	p->show();
	//p->showAge();

}


int main()
{
	test01();

	system("pause");
	return 0;
}

image

点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;


//this 可以解决命名冲突

class Person
{
public:
 //在调用它的时候会默认加以个 Person * this,而对象指针为NULL,相当于这里的 this 为NULL,
 //即这里用不了这个this,而如果不用这个this,那就还好,能跑!
	void show()
	{
		cout << "Person show" << endl;
	}

	void showAge()
	{
		//cout << m_Age << endl;//这里隐藏了 this->m_Age,而this 为 NULL,那就相当于 NULL->m_Age //这玩意能有值???
		//通常会加一个判断,来防止空指针访问成员函数
		if (this == NULL)
		{
			return;
		}
		cout << m_Age << endl;
	}

	int m_Age;
};

void test01()
{
	Person* p = NULL;//空指针
	p->show();
	//p->showAge();

}


int main()
{
	test01();

	system("pause");
	return 0;
}

const运算符修饰

const修饰成员函数,被修饰的函数叫常函数

  • 用const修饰的成员函数时,const修饰this指针指向的内存区域,成员函数体内不可以修改本类中的任何普通成员变量,
  • 当成员变量类型符前用mutable修饰时例外。
  • 常函数实际上修饰的是this指针: const Type * const this
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;


//this 可以解决命名冲突

class Person
{
public:
	Person()
	{
		//构造中修改属性
		//this指针永远指向自身
		//Person * const this
		//this = NULL;//被const修饰了,就不能改变指向了
		//指针指向的值此时还是可以修改的,如果想要值也不能被修改,那就要再加一个const,const Person * const this
		this->m_A = 0;
		this->m_B = 0;
	}

	void showInfo()const//常函数 不允许修改指针指向的值
	{
		//this->m_A = 100;//error 表达式必须是可修改的左值
		//相当于把this指针修饰成:const Person* const this
		this->m_B = 100;
		cout << "m_A " << m_A << endl;
		cout << "m_B " << m_B << endl;
	}

	int m_A;
	mutable int m_B;//就算是常函数,我还是执意要修改,就需要加一关键字叫:mutable
};

void test01()
{
	Person p1;
	p1.showInfo();

}


int main()
{
	test01();

	system("pause");
	return 0;
}

image

const修饰对象

const修饰对象(常对象)

  1. 常对象只能调用const的成员函数
  2. 常对象可访问 const 或非 const 数据成员,不能修改,除非成员用mutable修饰
  3. 常对象就是在对象前加入const修饰,:const Person p1;
  4. 常对象不能调用普通成员函数,可以调用常函数
点击查看代码
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;


//this 可以解决命名冲突

class Person
{
public:
	Person()
	{
		//构造中修改属性
		//this指针永远指向自身
		//Person * const this
		//this = NULL;//被const修饰了,就不能改变指向了
		//指针指向的值此时还是可以修改的,如果想要值也不能被修改,那就要再加一个const,const Person * const this
		this->m_A = 0;
		this->m_B = 0;
	}

	void showInfo()const//常函数 不允许修改指针指向的值
	{
		//this->m_A = 100;//error 表达式必须是可修改的左值
		//相当于把this指针修饰成:const Person* const this
		this->m_B = 100;
		cout << "m_A " << m_A << endl;
		cout << "m_B " << m_B << endl;
	}


	void show2()
	{

	}

	int m_A;
	mutable int m_B;//就算是常函数,我还是执意要修改,就需要加一关键字叫:mutable
};

void test01()
{
	Person p1;
	p1.showInfo();

	//2.常对象,不允许修改
	const Person p2;
	//p2.m_A = 100;//error 表达式必须是可修改的左值,不可写
	cout << "p2.m_A = " << p2.m_A << endl;//可读
	//p2.show2();//error 不兼容的类型限定符 
	//p2不被允许修改属性的值,而成员函数里可以修改属性的值,那编译器直接不让你调
	//即,常对象不能调用普通成员函数
	//那么如何能调该函数呢?
	//将函数变为常函数

}


int main()
{
	test01();

	system("pause");
	return 0;
}

友元

posted @ 2023-02-15 23:04  nullptrException  阅读(78)  评论(0)    收藏  举报