用人话讲C++————类与对象的知识进阶(4)

对象成员

在日常生活中,一个物品可能由多个零部件组成。比如,一台汽车由发动机、车轮、座椅组成,一只手机由芯片、外壳和显示屏组成。其中车轮、座椅、芯片、显示屏本身也是独立的个体,概括起来就是:一个对象中包含了其他对象。

对象成员的定义

对象成员简单地来说就是再定义一个新类时,可以用已有的类类型实例化对象作为新类的数据成员使用

class A{
	...
};

class B{
	...
};
class C{
public:
	...
private:
	int x;
	char y;
	A obj_a;
	B obj_b;
};	

C类中的数据成员有4个,分别是int类型的x、char类型的y、A类型的obj_a以及B类型的obj_b。其中x和y是普通数据成员,而obj_a和obj_b是类类型A和B实例化的对象,在此作为C的数据成员出现,称之为对象成员

对象成员和任何其他类成员一样,存在访问属性的问题。如果对象成员在新类中被定义为私有属性,则只能从新类的内部引入,如果将对象成员在新类中被定义为公有属性,就可以在新类的外部对其进行访问,但是对象成员本身的私有属性成员仍然是不可直接访问的。

之前的特性任然生效,但是对象不一样的就是对象,你找的对象绝对不是一个行为,而是和你“同病相怜”的一类人。格局打开,你的对象可以是条狗类定义出来真爱,也可以是二次元类中定义出来的。引入外来类当成自己的“属性”的,称这个“数据成员”为对象。

对象成员的构造与析构

对象成员也是类类型实例化的结果,因此当一个类中包含对象成员时,在实例化对象时也要考虑其对象的实例化过程。

与普通对象一样,对象成员在创建时需要调用构造函数,在生命周期结束时需要调用析构(火化)函数。

这个成员对象的构造和析构的顺序,读程题考的很多


#include<iostream>
using namespace std;
class A {
public:
	A() {
		cout << "创建A" << endl;
	}
	~A() {
		cout << "析构A" << endl;
	}
};
class B {
private:
	A a;
public:
	B() {
		cout << "创建B" << endl;
	}
	~B() {
		cout << "析构B" << endl;
	}
};
int main() {
	B b;
}

运行结果如下所示:
创建A
创建B
析构B
析构A

从上面的代码可以得出下面这些结论

  • 对象与它内部的对象成员具有相同的生命期。当对象被创建时,对象成员也会被创建,对象析构时,对象成员也会一同被析构。
  • 创建一个对象时,构造函数的调用次序是:首先调用对象成员的构造函数,再调用对象自身的构造函数。析构时的调用顺序完全相反。

在这个类创建时,会先存在它内部数据的存在,好比对象,得先有对象,才能在这个类刚开始就有对象;“短命的你”挂掉以后,你的对象不一定没那么早挂,毕竟你的存在依赖你对象的存在,所以短命的类挂掉之后,它的对象才挂掉。

在实际应用中,大多数的类类型通常是需要带参数的构造函数,那么对于需要传递参数的构造函数来说,这个传递参数的过程放在新类构造函数的初始化列表中解决。其格式为“成员对象名(实际参数表)”

这个也叫有参构造,就像地主家的孩子和贫民的孩子。地主家的孩子就是“有参构造”,出生以后很多东西不需要自己动手(成员函数)去获得;而贫民的孩子几乎全部的东西都要自己去获得(成员函数)。

#include<iostream>
#include<string.h>
using namespace std;
class CDate {
private:
	int Date_Year, Date_Month, Date_Day;
public:
	CDate(int y = 2000, int m = 1, int d = 1);
	~CDate();
	void Display();
};
CDate::CDate(int y, int m, int d) {
	cout << "Constructor called." << endl;
	Date_Year = y;
	Date_Month = m;
	Date_Day = d;
}
CDate::~CDate() {
	cout << "Destructor called.\n";
}
void CDate::Display() {
	cout << Date_Year << "-" << Date_Month << "-" << Date_Day << endl;
}
class Croster {
private:
	string name;
	CDate birthday;
public:
	Croster(string na, int y, int m, int d);
	void Display();
	~Croster();
};
Croster::Croster(string na, int y, int m, int d) :birthday(y,m,d){
	cout << "Croster constructor called.\n";
	name = na;
}
void Croster::Display() {
	cout << name << endl;
	birthday.Display();
}
Croster::~Croster() {
	cout << "Croster deconstructor called.\n";
}
int main() {
	Croster stuA("阿巴怪", 2001, 1, 29);
	stuA.Display();
	return 0;
}

运行结果如下所示
Constructor called.
Croster constructor called.
阿巴怪
2001-1-29
Croster deconstructor called.
Destructor called.

静态成员

在类定义中,除了普通数据成员、成员函数和对象成员外,还可以用关键字static声明静态成员。这些静态成员可以在同一个类的不同对象之间提供数据共享,不管这个类创建了多少个对象,但静态成员只有一份复制(副本),为所有属于该类的对象所共享。静态成员包括静态数据成员和静态成员函数。

静态是个ex的家伙。我的总结概括是,看他被调用到了几次,它的变化随他被调用而变化。不需要管是谁调用的,什么地方被调用的,看的是他被调用就好了(我是乐迪,何时何地,准时送达

静态数据成员

在处理复杂对象时,经常需要同类对象之间共享数据。例如,设计一个报名表,需要知道报名表中剩余学生的名额个数,当然可以选择定义一个全局变量来统计剩余学生的名额数,或者设计一个普通的数据成员用来保存总数,但问题是,使用全局变量,数据安全性得不到保障,而类的普通数据成员信息又不易同步和更新,若使用静态数据成员就能很好的解决这个问题。

//静态变量的初始化
类型 类名::静态数据成员名 = 初始值;

注意:静态成员的初始化在类外,不然类内创建的话,每一次创建一个类的变量,就会调用一次,出现了本质的逻辑错误。

类外初始化静态数据成员时,不需要再使用static关键字,但是需要使用“类名::”来限定承彦明,以使得编译器理解此处使用的时某个类的静态变量,否则,编译器会理解为只是创建一个与类无关的全局变量。

#include<iostream>
#include<string.h>
using namespace std;
class Croster {
public:
	static int Count;
private:
	string name;
	int Math;
	int English;
	int Sum;
public:
	Croster(string na = "undef", int m = 100, int e = 100);
	void Display();
	int Cumulation();
};
int Croster::Count = 100;
Croster::Croster(string na , int m, int e) :name(na),Math(m),English(e){
	cout << "欢迎新同学" << endl;
	Sum = Math + English;
	Count--;
}
void Croster::Display() {
	cout << name << endl;
	cout << "Math:" << Math << endl;
	cout << "English:" << English << endl;
	cout << "Sum:" << Sum << endl;
}
int Croster::Cumulation() {
	Sum = Math + English;
	return Sum;
}
int main() {
	cout << "Number of all student = " << Croster::Count << endl;
	Croster list[3];
	cout << "Number of all student = " << list[1].Count << endl;
	Croster stu_A;
	cout << "Number of all student = " << stu_A.Count << endl;
	cout << "Number of all student = " << Croster::Count << endl;
	return 0;
}

代码运行如下所示
Number of all student = 100
欢迎新同学
欢迎新同学
欢迎新同学
Number of all student = 97
欢迎新同学
Number of all student = 96
Number of all student = 96

代码中的静态数据成员Count为公有属性,因此可以通过类名或对象名直接访问。而引用静态数据成员Count的方式对结果没有任何影响。该成员的值是相同的,等于余下的名额。值得注意的是,main函数第一行代码并没有创建任何对象,此时只能以“类名::公有静态数据成员”的形式访问,因为即使没有创建任何类对象,类的静态成员也是存在的。

静态成员函数

在上面的讲解中,似乎只能把静态数据成员定义为public,但是为了保障数据安全,肯定得定义为private,但是定义为private无法使用普通的成员函数调用,或者说难以理清其中的逻辑关系,所以需要借助静态成员函数

将某个成员函数声明为static,该函数将独立于本类的任何实例。静态成员函数的优点是:即使本类没有创建任何对象,静态成员函数也存在并可以被调用,在这种情况下静态成员函数只能访问静态数据,不允许访问静态成员函数。

#include<iostream>
#include<string.h>
using namespace std;
class Croster {
private:
	string name;
	int Math;
	static int Sum;
	static int Count;
public:
	
	Croster(string na = "undef", int m = 100);
	static void Display();
};
int Croster::Count = 100;
int Croster::Sum = 0;
Croster::Croster(string na, int m):name(na),Math(m) {
	cout << "欢迎新同学" << endl;
	Count--;
	Sum += Math;
}
void Croster::Display() {
	cout << "Sum : " << Sum << endl;
	if (Count == 100)
		cout << "Average = 0" << endl;
	else
		cout << "Average = " << ((double)Sum )* 1.0 / (100 - Count) << endl;
}
int main() {
	Croster::Display();
	Croster list[3] = {
		Croster("张三",95),Croster("李四",90),Croster("王五",92)
	};
	list[1].Display();
	Croster stu_A("李梅");
	stu_A.Display();
	return 0;
}

代码运行结果如下所示:
Sum : 0
Average = 0
欢迎新同学
欢迎新同学
欢迎新同学
Sum : 277
Average = 92.3333
欢迎新同学
Sum : 377
Average = 94.25

常对象

在程序设计中,经常需要创建固定的类对象,当希望提供的信息不要被修改,就可以考虑将对象定义为常对象,也就是在创建这个对象时进行如下定义。

const Croster stu_A("李梅");

如果将某个实例声明为const,则编译器不允许该对象调用任何可能改变它的成员函数。与基本数据类型的常量一样,在定义常对象必须进行初始化,而且不能修改其对象的数据成员值。这就造成常对象不能调用类中的普通成员函数,因为,普通函数可能会修改数据成员值,而常对象的任何数据成员值都不允许被修改。

#include<iostream>
#include<string>
using namespace std;
class Croster {
private:
	string name;
	int Math;
	double Score;
	double GPA;
public:
	Croster(string na = "undef", int m = 100, double s = 3);
	double GetGPA();
	void Display();
};
Croster::Croster(string na, int m, double s) :name(na), Math(m), Score(s){

}
double Croster::GetGPA() {
	GPA = Math / 100.0 * Score;
	return GPA;
}
void Croster::Display() {
	cout << name << "get " << Math << endl;
	cout << "Your GPA is " << GetGPA() << endl;
}
int main() {
	const Croster stu_A("老王", 92, 3);
	stu_A.Display();
	return 0;
}

在代码编写的时候就会跳出报错
在这里插入图片描述

通常this指针可以理解为是一个指针常量(A const *this),在对象调用成员函数时,相当于用对象的地址初始化该指针常量,这样在成员函数中访问数据成员皆隐含使用this指针,声明常对象时,其this指针就理解为指向常量的指针常量(const A const *this),此时若传递给成员函数的this指针没有被指定为const,则编译器不允许调用。

常成员

为了解决数据共享与数据安全的统一,C++通过适时地巧用关键字const,对相应的数据进行保护。例如,在引用或指针形式参数的最前面加上const关键字,第一重保障就起作用了,因为该参数在函数中不能被修改,也就不会引起对应实参的变化。

那么,在面向对象的程序设计中,有时要求类内的某一数据成员是不能被修改的,常数据成员可以满足这一要求;有时,要求类的成员函数只能访问类内的其他成员而不允许修改,这就需要定义常成员函数。

常数据成员

如普通的常量一样,在类中有时需要用到常量。而这些常量如果按以往的方法定义为全局常量,显然不利于代码的移植。在类中,允许定义常数据成员,仅在本类中起作用,方便了类的移植。

//常数据成员在类内的定义形式
const 类型名 常数据成员;

与基本数据类型的常量一样,在定义常数据成员时必须进行初始化,假设在Croster类中增加一个常量PI作为数据成员,能不能在类的构造函数体中用这样的语句来定义?

const double PI = 3.14 ;这条定义语句放在类外是完全没有问题的,但是如果放在构造函数体中,就会提示如下的报错信息。

error C2864: "Croste::PI":只有静态常量整型数据成员才可以在类中初始化
error C2758:"Croster::PI":必须在构造函数的成员初始值设定项列表中初始化

也就是说,常数据成员的初始化只能在构造函数的初始化列表中进行,不能在构造函数的函数体中用赋值语句实现,而普通数据成员两种方式均可。

#include<iostream>
#include<string>
using namespace std;
class Croster {
private:
	string name;
	int Math;
	const double Score;
	double GPA;
public:
	Croster(string na = "undef", int m = 100, double s = 3);
	double GetGPA();
	void Display();
};
Croster::Croster(string na, int m, double s) :name(na), Math(m), Score(s){

}
double Croster::GetGPA() {
	GPA = Math / 100.0 * Score;
	return GPA;
}
void Croster::Display() {
	cout << name << "get " << Math << endl;
	cout << "Your GPA is " << GetGPA() << endl;
}
int main() {
	Croster stu_A("老王", 92, 3);
	stu_A.Display();
	return 0;
}

常成员函数

为了使成员函数中的this指针指向常对象,必须在类定义中将该函数声明为const。换句话说,如果一个成员函数对类中的数据成员只做访问而不做直接或间接的修改,则最好将此成员函数说明为常成员函数,以明确表示它对数据成员的保护性。

类型 函数名(形式参数表) const;

这里的const是函数类型的一个组成部分,因此在常成员函数的原型声明及函数定义的首部都要使用关键字const。

关键字const可以作为其他成员函数重载的标志,例如,同一个类中有两个函数的原型声明:void Print();void Print() const;,二者都是正确的重载函数。

1、只能将成员函数声明为const,对普通的函数不能这样声明,这样的声明使该函数中的this指针指向常对象,则类中的数据成员不可以出现在赋值符号的左边。
2、常成员函数不能调用该类中未经关键字const修饰的普通成员函数。由于普通成员函数可以改变数据成员的值,如果允许被常成员函数调用,则说明常成员函数可以间接修改数据成员的值,显然,这与常成员函数保护本类内部数据成员的初中相左,因此不被允许。但是反过来,普通成员函数可以调用常成员函数。

#include<iostream>
#include<string>
using namespace std;
class Croster {
private:
	string name;
	int Math;
	const double Score;
	double GPA;
public:
	Croster(string na = "undef", int m = 100, double s = 3);
	double GetGPA() const;
	void Display() const;
	void Display();
};
Croster::Croster(string na, int m, double s) :name(na),Math(m),Score(s){
	GPA = Math / 100.0 * Score;
}
double Croster::GetGPA() const {
	return GPA;
}
void Croster::Display() {
	cout << "This is void Display()." << endl;
	cout << name << "get " << Math << endl;
	cout << "Your GPA is " << GetGPA() << endl;
}
void Croster::Display() const {
	cout << "This is void Display() const." << endl;
	cout << name << "get " << Math << endl;
	cout << "Your GPA is " << GetGPA() << endl;
}
int main() {
	const Croster stu_A("老王", 92, 3);
	Croster stu_B("老吴", 98, 3);
	stu_A.Display();
	stu_B.Display();
	return 0;
}

代码运行如下所示
This is void Display() const.
老王get 92
Your GPA is 2.76
This is void Display().
老吴get 98
Your GPA is 2.94

该程序中的Display()函数有重载的版本,一个是常成员函数,另一个是普通成员函数,通过函数首部的最后是否有const加以区分。
从运行结果可知,同样是调用Display()函数,常对象调用的一定是常成员函数void Display() const,而普通对象在调用时遵循这样的原则:如果有普通成员函数的重载版本,则首先会调用普通成员函数;否则,自动调用常成员函数,因为普通对象也是可以调用常成员函数的,


  • 注意:常成员函数无论是原型声明还是函数定义的首部,都不能省略const

友元

ps:大头来了

类的一个很重要的特点就是实现了封装和信息隐藏。在定义类时,一般都将数据成员声明为私有成员,以达到隐藏数据的目的,而在类的外部不能直接访问这些私有成员,只能通过类的公有成员函数间接访问,这样安全但有时比较麻烦。对于需要在类的外部直接访问类的私有数据成员的情况,希望有一种新的途径,即在不改变类的数据成员安全性的前提下,能有一个该类外部的函数或另一个类能够访问该类中的私有数据成员。在c++中,通过声明友元来实现这一功能

友元并没有增加数据的安全性,反而是在一定程度上破坏了原有类的数据安全
友元的作用主要是提高效率和方便编程
选择使用友元时要在效率与安全方面折中考虑
友元共有三种不同形式。
(1)一个不属于任何类的普通函数声明为当前类的友元,此函数称为当前类的友元函数
(2)一个其他类的成员函数声明为当前类的友元,此成员函数称为当前类的友元成员。
(3)另一个类声明为当前类的友元,此类称为当前类的友元。

友元函数

前言:
频繁的函数调用有一定的系统开销,如果能直接访问到数据成员,代码将变得更加简洁高效。

so…

欢迎友元函数的加入!(哎)

friend 函数返回类型 函数名(形式参数表):

友元是个很麻烦的东西,友元函数定义在类内,说明别的类内的函数能通过这个友元函数,获取定义友元函数类的各个数据成员。并且这个关系不是双向的。

我觉得我和老王是好朋友,所以老王“深深”知晓我的对象(bushi) ,而老王并没有认为我和他是好朋友,只是想了解我的对象(bushi ,所以我是并不知道老王的对象长什么样,甚至有没有都不知道(如果没公开的话)。

友元成员

除了普通函数可以作为某个类的友元外,A类的成员函数也可以作为B类的友元,此成员函数称为B类的友元成员。友元成员函数不仅可以访问自己所在类的所有成员,也可以帮助B类型参数访问B类型的所有成员,这样,可以使两个类共享数据、相互合作。

#include<iostream>
#include<string>
using namespace std;
class CDate;
class Croster {
private:
	string name;
	double GPA;
public:
	Croster(string na = "undef", double g = 3);
	void PrintReport(const CDate& date) const;
};
class CDate {
	int Date_Year, Date_Month, Date_Day;
public:
	CDate(int y = 2000, int m = 1, int d = 1);
	friend void Croster::PrintReport(const CDate& date) const;
};
CDate::CDate(int y, int m, int d) :Date_Year(y), Date_Month(m), Date_Day(d) {

}
Croster::Croster(string na, double g) :name(na), GPA(g) {

}
void Croster::PrintReport(const CDate& date) const {
	cout << name << "同学本学期获得的绩点为:" << GPA << endl;
	cout << date.Date_Year << " - " << date.Date_Month << " - " << date.Date_Day;
	cout << endl;
}
int main() {
	Croster stu("老王", 3.95);
	CDate date(2022, 7, 8);
	stu.PrintReport(date);
	//Croster类对象stu调用函数PrintReport也显示对象date的信息。
	return 0;
}

代码运行结果如下所示
老王同学本学期获得的绩点为:3.95
2022 - 7 - 8

前向声明仅是类型说明符,只能用于定义引用或指针,不可以定义对象。

友元类

不仅类的成员函数可以定义为另一个类的友元,一个类整体也可以声明为另一个类的友元。若A类被声明为B类的友元,则A类中的所有成员函数都是B类的友元成员,都可以访问B类的所有成员

//友元类的声明格式
friend 类名
#include<iostream>
#include<string>
using namespace std;
class Croster;
class CDate{
private:
	int Date_Year, Date_Month, Date_Day;
public:
	CDate(int y = 2000, int m = 1, int d = 1);
	friend Croster;
};
CDate::CDate(int y, int m, int d) :Date_Year(y), Date_Month(m), Date_Day(d) {}
class Croster {
private:
	string name;
	double GPA;
public:
	Croster(string na = "undef", double g = 3);
	void PrintReport(const CDate& date) const;
};
Croster::Croster(string na, double g) :name(na), GPA(g) {}
void Croster::PrintReport(const CDate& date) const {
	cout << name << "同学本学期获得绩点为:" << GPA << endl;
	cout << date.Date_Year << "-" << date.Date_Month << "-" << date.Date_Day << endl;
}
int main() {
	Croster stu("老王", 3.95);
	CDate date(2023, 7, 9);
	stu.PrintReport(date);
	return 0;
}

代码运行结果如下所示:
老王同学本学期获得绩点为:3.95
2023-7-9

  • 友元关系是单向的,不具有交互性
  • 有缘关系也不具备传递性,及A类将B类声明为友元,B类将C类声明为友元,此时C类并不是A类的友元

总之,友元机制是C++对垒的封装机制的补充。通过这一机制,一个类可以赋予某些函数以特权来直接访问其私有成员。类似于在一个四周密闭的盒子上打开一个小口,外界可以透过小孔来窥探盒子里密码。要特别注意的是,无论A类声明了哪种形式的友元,虽然这些友元都拥有访问A类所有成员的特权,但他们都不是A类成员。
使用友元可以避免频繁调用类的接口函数,提高程序的运行速度,从而也提高了程序的运行效率。因为有了友元,外界在需要访问类的私有成员时,不再需要调用公有成员函数,特别是在频繁使用类的私有成员时,可以节省系统的开销。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

优降宁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值