对象的赋值和复制静态成员

对象的赋值和复制静态成员,第1张

对象的赋值和复制/静态成员 对象的建立和释放

对象的建立和释放:在需要用到对象时才建立对象,在不需要用该对象时就撤销它,释放它所占的内存空以供别的数据使用,这样可以提高内存空间的利用率可以用new运算符动态地分配内存,用delete运算符撤销对象。用new运算符动态地分配内存后,将返回一个指向新对象的指针的值,即所分配的内存空间的起始地址。用户可以获得这个地址,并通过这个地址来访问这个对象。在执行new运算时,如果内存量不足,无法开辟所需的内存空间,目前大多数C++编译系统都使new返回一个0指针值(NULL)。只要检测返回值是否为0,就可判断分配内存是否成功。在执行delete运算符时,在释放内存空间之前,会自动调用析构函数,完成有关善后清理 对象的赋值和复制 对象的赋值

如果对一个类定义了两个或多个对象,则这些同类的对象之间可以互相赋值,或者说,一个对象的值可以赋给另一个同类的对象。这里所指的对象的值是指对象中所有数据成员的值。对象之间的赋值也是通过赋值运算符"=“进行的。本来,赋值运算符”="只能用来对单个的变量赋值,现在被扩展为两个同类对象之间的赋值,这是通过对赋值运算符的重载实现的。对象赋值的一般形式为:对象名1 = 对象名2;.此时对象1和对象2必须属于同一类

例:将一个立方体对象的值赋给另一个立方体对象

#include
using namespace std;
class Box
{
public:
	Box(int = 10, int = 10, int = 10); //声明有默认参数的构造函数
	int volume();
private:
	int height; //高
	int width; //宽
	int length; //长
};

Box::Box(int h, int w, int len) //定义构造函数
{
	height = h;
	width = w;
	length = len;
}

int Box::volume() //定义volume函数
{
	return height * width * length; //返回体积
}

int main() 
{
	Box box1(15, 30, 25), box2; //定义两个对象box1和box2
	cout << "The volume of box1 is " << box1.volume() << endl;
	box2 = box1; //将box1的值赋给box2
	cout << "The volume of box2 is " << box2.volume() << endl;
	return 0;
}

运行结果:

说明:

    对象的赋值只对其中数据成员赋值,而不对成员函数赋值。数据成员是占存储空间的,不同对象的数据成员占有不同的存储空间,赋值的过程是将一个对象的数据成员在存储空间的状态复制给另一对象的数据成员的存储空间。而不同对象的成员函数是同一个函数代码段,既不需要也没法向它们赋值。类的数据成员中不能包括动态分配的数据,否则在赋值时可能出现严重后果
对象的复制

对象的复制的一般形式为:类名 对象2(对象1);可以看到:它与定义对象的方式类似,但是括号中给出的参数不是一般的变量,而是对象。在建立对象时调用一个特殊的构造函数——复制构造函数
例:复制构造函数的定义

Box::Box(const Box& b)
{
	height = b.height;
	width = b.width;
	length = b.length;
}

复制构造函数也是构造函数,但它只有一个参数,这个参数是本类的对象(不能是其他类的对象),而且采用对象的引用形式(一般约定加const声明,使参数值不能改变,以免在调用函数时因不慎而使对象值被修改)。此复制构造函数的作用就是将实参对象的各数据成员的值一一赋给新的对象中的成员的值

C++还提供另外一种方便用户的复制形式,用赋值号代替括号
其一般形式是:类名 对象名1 = 对象名2;
例:

int main() 
{
	Box box1(15, 30, 25);
	cout << "The volume of box1 is " << box1.volume() << endl;
	Box box2 = box1, box3 = box2; 
	cout << "The volume of box2 is " << box2.volume() << endl;
	cout << "The volume of box3 is " << box3.volume() << endl;
	return 0;
}

运行结果:

对象的复制和赋值的区别

对象的赋值是对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋值。而对象的复制则是一个从无到有地建立一个新的对象,并使它与一个已有的对象完全相同(包括对象的结构和成员的值)在形式上

类名(形参表列); //普通构造函数的声明,如Box(int h,int w,int len);
类名(类名 &对象名); //复制构造函数的声明,如Box(Box &b);

在建立对象时,实参类型不同。系统会根据实参的类型决定调用普通构造函数或复制构造函数

Box box1(12,15,16); //实参为整数,调用普通构造函数
Box box2(box1); //实参是对象名,调用复制构造函数

在什么情况下被调用
普通构造函数在程序中建立对象时被调用
复制构造函数在用已有对象复制一个新对象时被调用,在以下3 种情况下需要复制对象:

    程序中需要新建立一个对象,并用另一个同类的对象对它初始化当函数的参数为类的对象时。在调用函数时需要将实参对象完整地传递给形参,也就是需要建立一个实参的拷贝,这 就是按实参复制一个形参,系统是通过调用复制构造函数 来实现的,这样能保证形参具有和实参完全相同的值。如:
void fun(Box b) //形参是类的对象
{  }

int main()
{
	Box box1(12, 15, 18);
	fun(box1); //实参是类的对象,调用函数时将复制一个新对象b
	return 0;
}
    函数的返回值是类的对象。 在函数调用完毕将返回值带回函数调用处时。此时需要将函数中的对象 复制一个临时对象并传给该函数的调用处。如:
Box f() //函数f的类型为Box类类型
{
	Box box1(12, 15, 18);
	return box1; //返回值是Box类的对象
}

int main()
{
	Box box2; //定义Box类的对象box2
	box2 = f(); //调用f函数,返回Box类的临时对象,并将它赋值给box2
	return 0;
}

以上几种调用复制构造函数都是由编译系统自动实现的,不必由用户自己去调用

静态成员

全局变量能够实现数据共享。如果在一个程序文件中有多个函数,在每一个函数中都可以改变全局变量的值,全局变量的值为各函数共享。但是用全局变量的安全性得不到保证,由于在各处都可以自由地修改全局变量的值,很有可能偶一失误,全局变量的值就被修改,导致程序的失败。因此在实际工作中很少使用全局变量。如果想在同类的多个对象之间实现数据共享,也不要用全局对象,可以用静态的数据成员。

使用类的静态成员的优点是:

    静态成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突静态成员可以实施封装,可以是私有成员,而全局对象不可以静态成员是特定类关联的,结构清晰
静态数据成员

静态数据成员是一种特殊的数据成员。它以关键字static开头。

例:

class Box
{
public:
	int volume;
private:
	static int height; //把height定义为静态的数据成员
	int width;
	int length;
};

如果希望各对象中的数据成员的值是一样的,就可以把它定义为静态数据成员,这样它就为所有对象所共有,而不只属于某个对象的成员,所有对象都可以引用它。静态的数据成员在内存中只占有一份空间(而不是每个对象都分别为它保留一份空间)。每个对象都可以引用这个静态数据成员。静态数据成员的值对所有对象都是一样的。如果改变它的值,则在各对象中这个数据成员的值都同时改变了。这样可以节约空间,提高效率。

说明:

    如果只声明了类而未定义对象,则类的一般数据成员是不占内存空间的,只有在定义对象时,才为对象的数据成员分配空间。但是静态数据成员不属于某一个对象,在为对象所分配的空间中不包括静态数据成员所占的空间。静态数据成员是在所有对象之外单独开辟空间。只要在类中定义了静态数据成员,即使不定义对象,也为静态数据成员分配空间,它可以被引用。如果在一个函数中定义了静态变量,在函数结束时该静态变量并不释放,仍然存在并保留其值。静态数据成员不随对象的建立而分配空间,也不随对象的撤销而释放(一般数据成员是在对象建立时分配空间,在对象撤销时释放)。静态数据成员是在程序编译时被分配空间的,到程序结束时才释放空间。静态数据成员可以初始化,但只能在类体外进行初始化。
    其一般形式为:数据类型 类名::静态数据成员名 = 初值。只在类体中声明静态数据成员时加static,不必在初始化语句中加static
    如:int Box::height = 10; //表示对Box类中的数据成员初始化
    注意:不能用参数初始化表对静态数据成员初始化
    如:Box(int h, int w, int len) :height(h) {} //❌
    如果未对静态数据成员赋初值,则编译系统会自动赋予初值0静态数据成员既可以通过对象名引用,也可以通过类名来引用有了静态数据成员,各对象之间的数据有了沟通的渠道,实现数据共享,因此可以不使用全局变量。全局变量破坏了封装的原则,不符合面向对象程序的要求。静态成员函数就是专门为了访问静态数据成员的,静态成员函数不能被声明为const。

例:用立方体类Box定义两个对象,引用不同对象中的静态数据成员

#include
using namespace std;
class Box
{
public:
	Box(int, int);
	int volume();
	static int height; //把height定义为公用的静态数据成员
	int width; //宽
	int length; //长
};

Box::Box(int w, int len) //通过构造函数对width和length赋初值
{
	width = w;
	length = len;
}

int Box::volume() //定义成员函数volume
{
	return height * width * length; //返回体积
}

int Box::height = 10; //对静态数据成员height初始化

int main() 
{
	Box a(15, 20), b(20, 30); //建立两个对象
	cout << a.height << endl; //通过对象名a引用静态数据成员height
	cout << b.height << endl; //通过对象名b引用静态数据成员height 
	cout << Box::height << endl; //通过类名引用静态数据成员height
	cout << a.volume() << endl; //调用volume函数,计算体积,输出结果
	return 0;
}

程序分析:

将height定义为公用的静态数据成员,所以在类外可以直接引用。可以看到,在类外可以通过对象名访问公用的静态数据成员,也可以通过类名引用静态数据成员。即使没有定义类对象,也可以通过类名访问静态数据成员。这说明静态数据成员并不是属于对象的,而是属于类的,但类的对象可以引用它。如果静态数据成员被定义为私有的,则不能在类外直接引用,而必须通过公用的成员函数引用

运行结果:

注意:公用静态数据成员与全局变量的不同,静态数据成员的作用域只限于定义该类的作用域内(如果是在一个函数中定义类,那么其静态数据成员的作用域就是此函数内)。在此作用域内,可以通过类名和域运算符"::"引用静态数据成员,而不论类对象是否存在。 静态成员函数

成员函数也可以定义为静态的,在类中声明函数的前面加static就成了静态成员函数。如:static int volume();和静态数据成员一样,静态成员函数是类的一部分而不是对象的一部分。如果要在类外调用公用的静态成员函数,要用类名和域运算符"::"。如:Box::volume();实际上也允许通过对象名调用静态成员函数,如:a.volume();。但这并不意味着此函数是属于对象a的,而只是用a的类型而已与静态数据成员不同,静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员当调用一个对象的成员函数(非静态成员函数)时,系统会把该对象的起始地址赋给成员函数的this指针。而静态成员函数并不属于某一对象,它与任何对象都无关,因此静态成员函数没有this指针。既然它没有指向某一对象,就无法对一个对象中的非静态成员进行默认访问(即在引用数据成员时不指定对象名)。可以说,静态成员函数与非静态成员函数的根本区别是:非静态成员函数有this指针,而静态成员函数没有this指针。由此决定了静态成员函数不能访问本类中的非静态成员。静态成员函数可以直接引用本类中的静态成员,因为静态成员同样是属于类的,可以直接引用。在C++程序中,静态成员函数主要用来访问静态数据成员,而不访问非静态成员。

例:

cout << height << endl;	//若height已声明为static,则引用本类中的静态成员,合法
cout << width << endl; //若width是非静态数据成员,不合法

但是,并不是绝对不能引用本类中的非静态成员,只是不能进行默认访问,因为无法知道应该去找哪个对象。如果一定要引用本类的非静态成员,应该加对象名和成员运算符"."。如:cout << a.width << endl; //引用本类对象a中的非静态成员

例:统计学生平均成绩。使用静态成员函数

#include 
using namespace std;
class Student //定义Student类
{
public:
    Student(int n, int a, float s) :num(n), age(a), score(s) {} //定义构造函数
    void total(); //声明成员函数
    static float average(); //声明静态成员函数
private:
    int num;
    int age;
    float score;
    static float sum; //静态数据成员sum(总分)
    static int count; //静态数据成员count(计数)
};

void Student::total() //定义非静态成员函数
{
    sum += score;    //累加总分
    count++; //累计已统计的人数 
}

float Student::average() //定义静态成员函数
{
    return sum / count;
}

float Student::sum = 0; //对静态数据成员初始化
int Student::count = 0; //对静态数据成员初始化

int main()
{
    Student stud[3] = { //定义对象数组并初始化
   Student(1001,18,70),
   Student(1002,19,79),
   Student(1005,20,98) };
    int n;
    cout << "please input the number of students:";
    cin >> n; //输入需要求前面多少名学生的平均成绩
    for (int i = 0; i < n; i++)  stud[i].total(); //调用n次total函数
    cout << "The average score of " << n << " students is " << stud[0].average() << endl;//调用静态成员函数
    return 0;
}

程序分析:

total是公有的成员函数,其作用是将一个学生的成绩累加到snm中。公用的成员函数可以引用本对象中的一般数据成员(非静态数据成员),也可以引用类中的静态数据成员。score是非静态数据成员,sum和count是静态数据成员。在main函数中,引用total函数要加对象名(今用对象数组元素名),引用静态成员函数average函数要用类名或对象名。

运行结果:

在C++程序中最好养成这样的习惯:只用静态成员函数引用静态数据成员,而不引用非静态数据成员。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5702996.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存