类和对象5【C++】

本文详细介绍了C++中对象的动态建立和释放,包括new和delete操作符的使用,以及如何初始化和释放动态内存。接着讨论了类的组合,解释了如何通过构造函数的初始化列表来初始化内嵌对象。此外,还深入探讨了静态成员,包括静态数据成员和静态成员函数,它们在类的所有对象间共享。最后,介绍了友元的概念,包括友元函数和友元类,以及它们在访问私有和保护成员方面的特殊性。
摘要由CSDN通过智能技术生成

类和对象5【C++】

一、对象的动态建立和释放

        动态内存分配是指在程序运行期间根据实际需要随时申请内存,并在不需要时释放。

newdelete

(1)内存空间申请

(1)new 操作符:表示从堆内存中申请一块空间。

(2)返回值:

        申请成功:返回所申请到的空间的首地址

        申请失败:返回空指针(NULL)

(3)new 的三种形式:​​​​​​

  • new 数据类型
  • new 数据类型 (初始化值)
  • new 数据类型[常量表达式]

Example:

int* a = new int;
int* b = new int(3);	// 初始化为3
int* c = new int();		// 初始化为0
int* str = new int[10];

(4)利用动态内存分配在堆中创建对象,系统自动调用相应的构造函数来初始化对象。

Date* date1 = new Date;
date1->Print();
Date* date2 = new Date(2021,7,1);
date2->Print();

(2)内存空间释放

(1)delete 操作符:表示将从堆内存中申请的一块空间返回给堆。

(2)delete 的两种形式:

  • delete 指针名
  • delete [ ]指针名
int* a0 = new int;
float* a1 = new float(1.1);
int* a2 = new int[100];
Date* p = new Date(2021, 7, 1);
delete a0;
delete a1;
delete[]a3;
delete p;

(3)注意: 

        a. new 和 delete 需要配套使用:

        b. 在使用 delete 来释放一个指针所指的空间时,必须确保这个指针所指的空间使用

new 申请的,并且只能释放这个空间一次。

int i;
int* a = &i;
delete a;            // 错误,空间不是 new 申请的

int* b = new int(3);
delete b;            // 正确,空间时 new 申请的
delete b;            // 错误,只能释放一次

        c. 如果在程序中使用了 new 申请空间,就应该在结束程序前释放所以的申请空间。

int* a = new int;
*a = 2;
a = new int;    // 错误,产生了内存泄漏

        d. 当一个指针没有指向合法的空间,或者指针所指的内存已经释放以后,最好将指针的

值设为 NULL。

        e. 在堆中创建数组时,其元素只能初始化为元素类型的默认值,不能使用初始化表提供

不相同的初值。

        如果是类类型的话,系统会调用默认构造函数对其初始化;如果是其他类型的话,默认

情况下不会对其初始化,如果需要初始化,只能在表达式后面加一对圆括号。

int* a0 = new int[5];
Date* date = new Date[5];
int* a1 = new int[5] = {1,2,3,4,5};
int* a2 = new int[5]();

二、类的组合

        类的数据成员是类类型,有类对象作为成员称为组合类。

通过构造函数的初始化表为内嵌对象初始化格式为:

类名 :: 构造函数(参数表): 内嵌对象1(参数表1),内嵌对象2(参数表2),...

{

        构造函数体

}

         组合类构造函数的执行顺序为:

(1)按内嵌对象的声明顺序依次调用内嵌对象的构造函数;

(2)然后执行组合类本身的构造函数。

Example:

【例题】一个学生类。数据成员包括学生的姓名、学号、性别、出生日期。

【分析】学生的姓名、性别可以用简单的数据类型表示,学生的学号和出生日期可以用

类对象表示。定义三个类:学号类、日期类和学号类。学生类是一个组合类,类的数据

成员包括其他类对象,因此学生类的构造函数中使用初始化表初始化类对象数据成员。

【参考代码】

#include <iostream>
#include <cstring>
using namespace std;
//日期类声明
class Date {
    public:
        Date(int y = 2021, int m = 1, int d = 1) {
            year = y; 
            month = m;
            day = d;
            cout << "调用日期类的构造函数!" << endl;
        }   
        Date(Date& s) {         // 日期类的拷贝构造函数
            year = s.year;
            month = s.month;
            day = s.day;
            cout << "调用日期类的拷贝构造函数!" << endl;
        }
        ~Date() {
            cout << "调用日期类的析构函数!" << endl;
        }
    private:
        int year;
        int month;
        int day;
};

//学号类声明
class StudentID {
    public:
        StudentID(int i) {
            value = i;
            cout << "构造学号:" << value <<"的学号类构造函数!"<< endl;
        }
        ~StudentID() {
            cout << "析构学号:" << value <<"的学号类析构函数!"<< endl;
        }
    private:
        int value;
};

//学生类声明
class Student {
    public:
        Student(char* na, char s, int i, Date& d);
        ~Student();
    private:
        char name[20];
        char sex;
        StudentID id;
        Date birthday;
};
Student::Student(char* na, char s, int i, Date& d) : id(i), birthday(d) {
    strcpy(name, na);
    sex = s;
    cout << "调用学生" << name << "的构造函数!" << endl;
}
Student::~Student() {
    cout << "调用学生" << name << "的析构函数!" << endl;
}
int main() {
    Date day1(2022, 7, 31);
    Student stud1("张三", 'm', 20010702, day1);//定义学生对象
    return 0;
}

 三、静态成员

        有时某个类的所有对象都需要访问一个共享的数据。

1. 问题的引入

        问题描述:一个有对象计数器的类,这个计数器对当前程序中共有多少个此类的对象进行计

数。计数方法很简单,创建对象时计数增加,删除对象时计数减少,这两个操作可以分别由构造函

数和析构函数完成。那么这个计数器应该用什么样的变量表示呢?

        解决方案:

方法一:使用数据成员。每个对象都有一个独立的数据成员,那么这个计数器在每个对象中都存在

一个,当程序中对象数目发生改变时,要追踪和更新每个计数器很麻烦也很困难,并且也容易出现

不一致的错误。

方法二:使用全局变量计数。这种方法比在对象中使用计数器更高效。但是全局变量是可以被任何

对象或者函数访问或修改,不仅仅是这个类的对象,使得安全性不能保证。

        类的静态数据成员为上述这个问题提供了更好的解决方按案。

静态数据成员被当作该类类型的全局变量。对非静态数据成员,每个对象都有自己的拷贝,而静态

数据成员对整个类只有一个,由这个类型的所有对象共享访问。

        和全局变量相比,静态数据成员有以下两个优点:

(1)静态数据成员没有进入程序的全局作用域,只是在类作用域中,不会和全局作用域中的其它

名字产生冲突;

(2)可以实现信息隐藏,静态成员可以是 private 成员,而全局变量不行。

2. 静态数据成员

(1)静态数据成员的特点:

  • 在每个类中只有一个拷贝,由该类的所有对象共同维护和使用;
  • 静态数据成员的值对每个对象都是一样的,但是它的值是可以更新的;
  • 只要对静态数据成员的值更新一次,保证所有对象存取更新后有相同的值。

(2)定义及初始化格式为: 

 static 类型标识符 静态数据成员名;

类型标识符 类名 :: 静态数据成员名 = 初始值;

 注意:

a. 从逻辑角度上来讲,静态数据成员从属于类,而非静态数据成员从属于对象;

b. 从物理角度上来讲,静态数据成员存放在静态存储区,由所有对象共享,生命周期不依赖于对

象。而非静态数据成员独立存放于各个对象当中,生命周期依赖于对象,随对象的创建而存在,随

对象的销毁而消亡;

c. 初始化在类体外进行,而且前面不加 static,以免与一般静态变量相混淆

d. 初始化时不加该成员的访问权限修饰符 private,public 等;

e. static 成员只能定义一次,所以定义一般不放在头文件中,而是放在包含成员函数定义的源文件

中。

(3)静态数据成员的访问

类名 :: 静态数据成员;

对象名. 静态数据成员;

Example: 

【例】在 Student 类中添加静态数据成员,保存学生的总数。.

#include <iostream>
#include <cstring>
using namespace std;
class Student {
	public:
		Student(int i, char* na);
		~Student();
		void Print();
		static int count;		//公有的静态数据成员
	private:
		int id;
		char name[20];
};
Student::Student(int i, char* na) {
	id = i;
	strcpy(name, na);
	count++;					//创建一个学生,学生数加1
}
Student::~Student() {
	count--;					//学生对象撤销时,学生数减1
}
void Student::Print() {
	cout << "姓名:" << name << " ," << "学号:" << id << endl;
}

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

void display() {
	Student stud3(10003, "王五");
	stud3.Print();
	cout << "学生数为:" << Student::count << endl;
}
int main() {
	Student stud1(10001, "张三");
	stud1.Print();
	cout << "学生数为:" << Student::count << endl;
	//通过类名引用静态数据
	Student stud2(10002, "李四");
	stud2.Print();
	cout << "学生数为:" << stud2.count << endl;
	//通过对象名引用静态数据
	display();
	cout << "学生数为:" << Student::count << endl;
	return 0;
}

3. 静态成员函数 

        静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员,没有this 指针。

(1)静态成员函数的格式为:

static 函数类型 静态成员函数名(参数表);

静态成员函数是在成员函数声明的前面加上 static

(2)调用静态成员函数格式为:

类名 :: 静态成员函数名(参数表);

对象名 . 静态成员函数名(参数表);

(3)静态成员函数的特点:

(1)对于公有的静态成员函数,可以通过类名或对象名来调用,而一般的非静态成员函数只

能通过对象名来调用。静态成员函数可以由类名通过符号“::”直接调用。

(2)静态成员函数可以直接访问该类的静态数据成员和静态成员函数,不能直接访问非静态

数据成员和非静态成员函数。如果静态成员函数中要引用非静态成员时,可通过对象来引

用。

【例】具有静态数据、函数成员的 Point

#include <iostream>
using namespace std;
class Point {
	public:
		Point(int X = 0, int Y = 0) {
			x = X;
			y = Y;
			count++;
		}
		Point(Point& p);	//拷贝构造函数
		int GetX() {
			return x;
		}
		int GetY() {
			return y;
		}
		static void GetC() {
			cout << "次数 = " << count << endl;
		}
	private:
		int x, y;
		static int count;
};
Point::Point(Point& p){
	x = p.x;
	y = p.y;
	count++;
}

int Point::count = 0;

int main() {
	Point A(4, 5);			//声明对象A
	cout << "Point A:(" << A.GetX() << "," << A.GetY() << ")	" ;
	A.GetC();				//输出对象号,对象名引用
	Point B(A);				//声明对象B
	cout << "Point B:(" << B.GetX() << "," << B.GetY() << ")	" ;
	Point::GetC();			//输出对象号,类名引用
	return 0;
}

4. 友元

(1)概念

        友元是 C++ 提供的一种破坏数据封装和数据隐藏的机制。友元可以理解为是类的“朋友”,它

可以访问类的保护和私有成员。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个

类,该类被称为友元类友元的作用在于提高程序的运行效率。但是,它破坏了类的封装性和隐藏

性,建议尽量不使用或少使用友元。

(2)友元函数

        友元函数是在类外定义的一个函数,不是类的成员函数。这个函数可以是普通的 C++ 函数,

或者是其他类的成员函数。即普通友元函数和友元成员函数。友元函数是定义在类外部,但需要在

类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字 friend。友元函数可

以访问类中的保护和私有成员。

        友元函数的定义:

普通友元函数的格式为:

friend 类型标识符 友元函数名(参数列表);

友元成员函数(即将其它类的成员函数声明为该类的友元函数)的格式为:

friend 类型标识符 其它类名 :: 友元函数(参数列表);

 Example:

【问题描述】

设计点类 Point,编写一个函数计算两点之间的距离。

【问题分析】

点类 Point 私有数据成员 x,y 表示点的坐标,定义成员函数 GetX() 和 GetY()分别得到点的

横坐标和纵坐标,定义一个普通函数计算两点间的距离时,得到两点的坐标值,计算距离。

【参考代码】

#include <iostream>
#include <cmath>
using namespace std;
class Point {
	public:
		Point(int X = 0, int Y = 0);
		double GetX();
		double GetY();
		//声明普通函数为类的友元函数
		friend double GetDistance(Point start, Point end); 
	private:
		double x, y;
};
Point::Point(int X, int Y) {
	x = X;
	y = Y;
};
double Point::GetX() {
	return x;
}
double Point::GetY() {
	return y;
}
//友元函数
double GetDistance(Point start, Point end) { 
	double d;
	//友元函数可以访问类的私有成员
	d = sqrt((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y));
	return d;
}
int main() {
	Point p1(0, 0), p2(3, 4);
	double d;
	d = GetDistance(p1, p2);
	cout << "两点之间的距离是:" << d << endl;
	return 0;
}

5. 友元类

        一个类可以作另一个类的友元称为友元类。当一个类作为另一个类的友元类时,意味着这个

类的所有成员函数都是另一个类的友元函数,可以访问另一个类的私有成员。

        声明友元类的格式为:

friend class 类名;

Example:  

【问题描述】

定义一个日期类,包括年、月、日和小时、分钟、秒。

【问题分析】

首先定义一个时间类 Time,而日期类 Date 的数据成员包括年(year)月(month)日(day)和一个 Time 的类对象。日期类 Date 的成员函数 disaplay() 显示日期和时间,要访问 Time 类的私有成员,因此将日期类 Date 声明为时间类 Time 的友元类。

【参考代码】

#include <iostream>
using namespace std;
class Time {
    public:
        Time(int h = 0, int m = 0, int s = 0);
        /*声明Date类为Time类的友元类,
        **则Date中所有的成员函数都是Time类的友元函数,
        **可以访问Time类的私有成员或保护成员。
        */
        //声明友元类
        friend class Date;   
    private:
        int hour;
        int minute;
        int sec;
};
Time::Time(int h, int m, int s) {
    hour = h;  
    minute = m;  
    sec = s;
}

class Date {
    public:
        Date(int y = 2021, int mon = 1, int d = 1, int h = 0, int m = 0, int s = 0);
        void disaplay();
    private:
        int month, day, year;
        Time t;
};
Date::Date(int y, int mon, int d, int h, int m, int s) :t(h, m, s) {
    month = mon; 
    day = d;    
    year = y;
}
void Date::disaplay() {
    cout << year << "/" << month << "/" << day<<" ";
    cout << t.hour << ":" << t.minute << ":" << t.sec << endl;
}
int main()
{
    Date d1(2022, 7, 31, 10, 13, 56);
    d1.disaplay();
    return 0;
}

注意:

友元关系不具有交换性,是单向的;

友元关系不具有传递性。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

周小周OvO

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

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

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

打赏作者

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

抵扣说明:

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

余额充值