C++学习笔记:(一)面向对象&类与对象

学习C++,一定要脚踏实地。

1.面向对象的概念

早期的计算机编程是基于面向过程的方法,例如实现算术运算1+1+2 = 4,通过设计一个算法就可以解决当时的问题。随着计算机技术的不断提高,计算机被用于解决越来越复杂的问题。一切事物皆对象,通过面向对象的方式,将现实世界的事物抽象成对象,现实世界中的关系抽象成类,帮助人们实现对现实世界的抽象与数字建模。通过面向对象的方法,更利于用人理解的方式对复杂系统进行分析、设计与编程。同时,面向对象能有效提高编程的效率,通过封装技术,消息机制可以像搭积木的一样快速开发出一个全新的系统。面向对象是指一种程序设计范型,同时也是一种程序开发的方法。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。

面向对象三大基本特征:封装、继承和多态。具体概念可以慢慢理解。其实这也是C和C++不一样的地方。如果我们要做一个学生管理系统,分别用C和C++来编程,就可以看到面向对象和面向过程的区别。面向对象和面向过程都是编程思想,C++可以用面向过程的思想编程,C也可以用面向对象的思想编程(但是很麻烦)。

//C:结构体
typedef struct Student
{
    long no;
    char name[10];
    float score;
};   
void findnumber(long no);
void findname(char name[]);
void Print();               

//C++:类
class Student
{
private:
    long no;
    char name[10];
    float score;
public:
    Student();
    void findnumber(long no);
    void findname(char name[]);
    void Print();
};

对比两者的定义来学习第一个概念封装:封装就是把客观事物封装成抽象的类。是对象和类概念的主要特性。 简单的说,一个类就是一个封装了数据以及对这些数据进行操作的代码的逻辑实体。在一个对象内部,某些函数成员或某些数据成员可以是私有的,不能被外界访问。C++中,封装是通过类来实现的。

再从两者的定义来看,C中函数和结构体是分开的,可以说这些函数是对外公开的,只要满足参数的要求就可以调用这个函数(这里不需要关注调用的结果,只是为了说明一个权限问题)。而C++中函数成员和数据成员会根据具体要求设置不同的权限,这样就对数据起到了保护作用,而且这些函数只能被本类对象调用。还有命名问题,若在C中有多个结构体都需要打印功能,就要防止命名相同的情况出现。而在C++中定义一个成员函数:void Student::Print()表明这个Print()是类Student的函数成员,其他的类也可以有Print()函数。例如:Student a;  Asd b;在使用Print()函数时分别是a.Print();和b.Print()。

这里简单了解封装的概念即可,学习完类和对象有助于学习继承和多态。现在简单说一下类和对象的关系。它们是一般和特殊、共性和特性的关系。如果知道“白马非马”,对理解类和对象是很有帮助的。类是一种数据类型,也是实现抽象类型的工具。类是对一组具有共同属性特征和行为特征的对象的抽象,并将相关数据和对数据的操作组合在一起。在Student类中,数据就是学生的学号,姓名和成绩,操作是按学号查找、按名查找以及打印。当定义了Student a1;那么a1就从抽象变成了具体,我们可以用它来表示一个学生,并将学号、姓名和成绩输入其中。

 

2.类与对象

2.1.1 类的定义格式:

class 类名
{
private:
    数据成员或成员函数
protected:
    数据成员或成员函数
public:
    数据成员或成员函数
};

类的成员包括两类成员,一类是代表对象属性的数据成员,另一类是实现对象行为的成员函数。成员函数的定义可以与声明同时在类里面完成,也可以在类外面完成。如果在类外面完成,就要使用作用域”::”符号告诉编译系统该函数所属的类。

 

访问权限:

访问权符也称为访问权限符或访问控制符,它规定类中说明的成员的访问属性,是C++语言实现封装的基本手段。C++提供了三种不同的访问权限符:private、protected和public。

(1)public(公有类型):表示这个成员可以被该类对象处在同一作用域内的任何函数使用。一般将成员函数声明为公有的访问控制。

(2)private(私有类型):表示这个成员能被它所在的类中的成员函数&该类的友元函数使用。

(3)protected(保护类型):表示这个成员只能被它所在类&从该类派生的子类的成员函数&友元函数使用。

具体使用的时候,应该根据成员的使用特点决定对其封装的程度。通常的做法是,将数据成员声明为私有或保护的,将对象向外界提供的接口或服务声明为公有的成员函数。如果某些数据成员在子类中也需要经常使用,则应该把这些数据声明为保护类型。例:

class Book
{
private:
    char title[20],author[10],publish[30];
    float price;
public:
    void Input();
    void Print();
};

(1)类声明中的三个关键字可以按任意顺序出现。为了程序更加清晰,应将私有成员、保护成员、和公有成员归类存放。默认的访问权限是私有的(private)。

(2)对于一个具体的类,类声明中的private、public和protected三个部分不一定都要出现,但至少应该有其中一个部分。

(3)数据成员可以是任意数据类型,但不能是自动(auto)、寄存器(register)或外部(extern)类型。

(4)由于类是一种数据类型,系统并不会为其分配内存空间,所以不能在类声明中给数据成员赋初值。

 

2.1.2成员函数的定义

一般代码量较少的成员函数可以在类中定义,而代码量较大的成员函数,通常在类中进行函数原型的说明,在类外对函数进行定义,其形式是:

返回类型  类名::函数名(参数表)
{
    //函数体
}

例:

void Book::Input()
{
    cin>>title>>author>>publish>>price;
}
void Book::Print()
{
    cout<<title<<” ”<<author<<” ”<<publish<<” ”<<price<<endl;  
}

(1)如果在类外定义成员函数,则应该在所定义的成员函数名前加上类名,在类名和函数名之间应加上作用域运算符“::”,它说明成员函数从属哪个类。

(2)定义成员函数时,对函数所带的参数,不但要说明它的类型,还要指出其参数名。

(3)在定义成员函数时,其返回类型一定要与函数原型声明的返回类型匹配。

 

2.2 对象的定义与使用

在C++中,声明了类,只是定义了一个新的数据类型,只有定义了类的对象,才能真正创建了这种数据类型的物理实体。对象是封装了数据结构及可以施加在这些数据结构上的操作的封装体。对象就是类的实际变量,一个具体的对象是类的一个实例。类和对象的关系,就类似于整型int和整型变量i的关系。类类型和整型int均是一般的概念,而对象和整型变量则代表具体的东西。与定义一般变量一样,可以定义类的变量。C++把类的变量称为类的对象,一个具体的对象也称为类的实例。

两种定义对象的方法:

(1)在声明类的同时,直接定义对象。

class Book
{
private:
   char title[20],author[10],publish[30];
   float price;
public:
   void Input();
   void Print();
}b1,b2; //表示定义b1,b2是Book类的对象。

(2)先声明类。然后在使用的时候再定义对象,其格式:

类名  对象名列表;

(1)必须定义了类以后,才能定义类的对象。

(2)声明了一个类就声明了一种类型,它并不能接收和存储具体的值,只能作为生成具体对象的一种“模板”,只有定义了对象后,系统才为对象分配存储空间。

(3)在声明类的同时定义的对象是一种全局对象,在它的生存期内任何函数都可以使用它,一直到整个程序运行结束。

对象的使用:

使用对象,就是向对象发送消息,请求执行它的某个方法,从而向外界提供所要求的服务。形式如下:

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

例如:Book b1(实参);

(1)在使用对象赋值语句进行赋值时,两个对象的类型必须相同。赋值兼容规则除外。

(2)两个对象间的赋值,仅仅使这些对象中的数据相同,而两个对象仍然是彼此独立的,各自有自己的空间。

(3)如果类中存在指针,则不能简单的将一个对象的值赋给另一个对象,否则可能会产生错误。

 

2.3 构造函数和析构函数

构造函数的功能是在创建对象时,给数据成员赋值,即对象的初始化。析构函数的功能是释放一个对象,在对象删除之前,用它来做一些内存释放等清理工作,它的功能与构造函数的功能正好相反。

定义构造函数原型的格式:

类名(形参列表);

在类外定义构造函数的格式为:

类名::类名(形参列表)
{
    //函数语句;
}

构造函数的特点:

(1)构造函数的名字必须与类名相同。

(2)构造函数可以有任意类型的参数,但是没有返回值类型,也不能指定为void类型。

(3)定义对象的时候,系统会自动调用构造函数。

(4)如果没有定义构造函数,系统会自动生成一个默认的构造函数,只负责对象的创建,不做初始化工作。

(5)构造函数可以重载。

 

定义析构函数的一般格式:

~类名();

析构函数的特点:

(1)析构函数没有参数,也没有返回值,而且不能重载。

(2)一个类中有且仅有一个析构函数,且应该为public。

(3)析构函数的功能是释放对象所占用的内存空间。

(4)析构函数与构造函数两者的调用次序相反,即最先构造的对象最后被析构,最后构造的对象最先被析构。

(5)如果一个对象被定义在一个函数体内,则当这个函数体结束时,该对象的析构函数会被自动调用。

 

2.4 内存的动态分配

用户存储区空间分为三个部分,程序区(代码区)、静态存储区(数据区)和动态存储区(栈区和堆区)。代码区存放程序代码,程序运行前就分配存储空间。数据区存放常量、静态变量、全局变量等。栈区存放局部变量、函数参数、函数返回值和临时变量等。堆区是程序空间中存放在的一些空闲存储单元,这些空闲存储单元组成堆。在堆中创建的数据对象为堆对象。当创建对象时,堆中的一些存储单元从未分配状态变为已分配状态;当删除所创建的堆对象时,这些存储单元从已分配状态又变成未分配状态。在C++中使用运算符new和delete来实现在堆内存区中进行数据的动态分配和释放。

 

2.4.1 运算符new

在C++程序中,运算符new的功能是实现内存的动态分配。在程序运行过程中申请和释放的存储单元称为堆对象。

new运算符的使用格式包括三种形式:

指针变量 =  new T;
指针变量 =  new T(初始值列表);
指针变量 =  new T[元素个数];

T是数据类型名,表示在堆中建立一个T类型的数据。

int *p;
float *p1;
p = new int(100);   //让p指向一个类型为整型的堆地址,该地址中存放数值100
p1 = new float;     //让p1指向一个类型为实型的堆地址

用new创建堆对象的格式:

类名 *指针名 = new 类名([构造函数参数]);

Complex *c1 = new Complex(1.1, 2.2);

(3)new返回一个指定的合法数据类型的内存空间的首地址,若分配不成功,则返回一个空指针。

(4)new可以为数组动态分配内存空间,这时应该在类型名后面指明数组大小。其中,元素个数是一个整型数值,可以是常数也可以是变量。

int n ,*p;
cin >> n;
p = new int[n];

(5)new不能对动态分配的数组存储区进行初始化。

(6)用new分配的空间,使用结束后只能用delete显式的释放,否则这部分空间将不能回收,从而造成内存泄漏。

 

2.4.2 运算符delete

运算符delete用来释放动态变量或动态数组所占用的内存空间。delete运算符的应用格式:

delete 指针变量名;   //释放指针p所指向的动态内存空间

delete [] 指针变量名;  //释放为数组动态分配的内存

(1)new和delete需要配套使用,如果搭配错了,程序运行时将会发生不可预知的错误。

(2)在用delete释放指针所指向的空间时,必须保证这个指针所指向的空间时用new申请的,并且只能释放一次,否则将产生指针悬挂问题。

(3)如果在程序中用new申请空间,应该在结束程序前释放所有申请的空间。这样才能保证堆内存的有效利用。

(4)当delete用于释放由new创建的数组的连续内存空间时,无论是一维数组还是多维数组,指针变量名前必须使用[],且[]内没有数字。

1.cpp

#include <iostream>
using namespace std;
class Point
{
	private:
		int x,y;
	public:
		Point(int a, int b);
		~Point();
};

Point::Point(int a, int b)
{
	cout << "constructor..." <<endl;
	x = a;
	y = b;
}

Point::~Point()
{
	cout << "destructor..." <<endl;
}

int main()
{
	Point *p = new Point(1, 3);
	delete p;
	return 0;
}

 

2.5 对象数组和对象指针

数组元素可以是基本数据类型的数据,也可以是用户自定义数据类型的数据。对象数组是指每一个数组元素都是对象的数组。对象数组的元素是对象,它不仅具有数据成员,而且还有函数成员。

声明对象数组的形式:

类名 数组名[下标表达式];

与基本类型的数组一样,在使用对象数组时也只能引用单个数组元素。通过对象,可以访问它的公有成员。对象数组的引用形式:

数组名[下标].成员函数

2.cpp

#include <iostream>
using namespace std;

class Circle
{
	private:
		double radius;
	public:
		Circle(double r);
		double Area();
		~Circle();
};

Circle::Circle(double r)
{
	cout << "construct..." <<endl;
	radius = r;
}

double Circle::Area()
{
	return 3.14*radius*radius;
}

Circle::~Circle()
{
	cout << "destruct..." <<endl;
}

int main()
{
	Circle c[3] = {1,3,5};
	int i;
	for(i = 0; i < 3; i++)
	{
		cout <<c[i].Area() <<endl;
	}

	return 0;
}

3.cpp

#include <iostream>
using namespace std;

class Point
{
	private:
		int x,y;
	public:
		Point(int a, int b);
		void Print();
};

Point::Point(int a, int b)
{
	x = a;
	y = b;
}

void Point::Print()
{
	cout << "(" << x << "," << y << ")" <<endl;
}

int main()
{
	Point a[3] = {Point(1,2), Point(3,4), Point(5,6)};
	int i;
	for(i = 0; i < 3; i++)
	{
		a[i].Print();
	}

	return 0;
}

 

2.5.1 对象指针

访问一个对象可以通过对象名访问,也可以通过对象地址访问。对象指针就是用于存放对象地址的变量。对象指针遵循一般变量指针的各种规则,声明对象指针的一般语法形式:

类名 *对象指针名;

使用对象指针也可以访问对象的成员:

对象指针名->成员名

与一般变量指针一样,对象指针在使用前必须先进行初始化。可以让它指向一个已经声明过的对象,也可以用new运算符动态建立堆对象。

circle *c1,c(3);
c1 = &c;
c1->Area();      //正确,c1指向的是一个已经声明过的对象
cirlce *c2 = new Circle(3);
c2->Area();      //正确,c2使用new运算符动态建立的堆对象c2
circle *c3;
c3->Area();      //错误,不能使用没有初始化的对象指针

2.cpp中main可改为:

int main()
{
  Circle c[3] = {1,3,5};
  Circle *p = c;
  for(; p < 3; p++)
  {
     cout << p->Area() <<endl;
  }
  return 0;
}

2.5.2 自引用指针this

this指针主要用在运算符重载和自引用等场合。

4.cpp

#include <iostream>
using namespace std;

class Square
{
	private:
		double a;
	public:
		Square(double a);
		double Area();
		void copy(Square &s);
};

Square::Square(double a)
{
	this->a = a;
}

double Square::Area()
{
	return a*a;
}

void Square::copy(Square &s)
{
	if(this == &s)
		return;
	*this = s;
}

int main()
{
	Square s1(3),s2(5);
	cout << "before copy" <<endl;
	cout << "s1 area is " << s1.Area() <<endl;
	cout << "after copy" <<endl;
	s1.copy(s2);
	cout << "s1 area is " << s1.Area() <<endl;
}

定义对象s1时通过构造函数将其数据成员初始化为3,因此调用Area函数输出9;当程序执行s1.copy(s2)时,对象s1调用成员函数copy,因此this指针指向s1。在copy函数中首先判断是不是对象在给自己赋值,如果是,就返回;否则,将形参s的值赋给this所指的对象s1。在Square::Sqare(double a)中,因为私有成员是a,而参数名也是a,此时要使用this指针告诉编译器是把参数a的值给私有成员a。

使用this指针时应该注意:

(1)this指针是一个const指针,不能再程序中修改或给它赋值。

(2)this指针是一个局部数据,它的作用域仅在一个对象的内部。

(3)静态成员函数不属于任何一个对象。在静态成员函数中没有this指针。

 

2.6 函数参数的传递机制

C语言中,函数的参数传递有两种方式:按变量值传递和按变量的地址传递,C++语言不仅可以实现上述两种参数传递方式,还可以进行引用传递。而且在C++语言中,不仅简单变量可以作为参数进行传递,对象也可以作为参数传递给函数,其方法与传递其他类型的数据一样。

2.6.1 使用对象作为函数参数

5.cpp

#include <iostream>
#include <iomanip>
using namespace std;
class Point
{
	private:
		int x,y;
	public:
		Point(int a, int b);
		void Add(Point p);
		void Print();
};

Point::Point(int a, int b)
{
	x = a;
	y = b;
}

void Point::Print()
{
	cout << "x:" << x << setw(5) << "y:" << y <<endl;
}

void Point::Add(Point p)
{
	p.x = p.x + 1;
	p.y = p.y + 1;
}

int main()
{
	Point ob(1,2);
	cout << "before add:";
	ob.Print();
	ob.Add(ob);
	cout << "after add:";
	ob.Print();
	return 0;
}

在执行Add函数时,由于作为参数的对象时按值传递的,也就是实参ob将自己的值对应的赋给形参p,在Add函数中对形参p的数据成员x和y值得修改结果并没有传回主程序中。

 

2.6.2 使用对象指针作为函数参数

6.cpp

#include <iostream>
#include <iomanip>
using namespace std;
class Point
{
	private:
		int x,y;
	public:
		Point(int a, int b);
		void Add(Point *p);
		void Print();
};

Point::Point(int a, int b)
{
	x = a;
	y = b;
}

void Point::Print()
{
	cout << "x:" << x << setw(5) << "y:" << y <<endl;
}

void Point::Add(Point *p)
{
	p->x = p->x + 1;
	p->y = p->y + 1;
}

int main()
{
	Point ob(1,2);
	cout << "before add:";
	ob.Print();
	ob.Add(&ob);
	cout << "after  add:";
	ob.Print();
	return 0;
}

对象指针作为参数传递给函数,是传地址,即形参和实参共享的是同一内存单元。因此,作为形参的对象,其数据的改变将影响实参对象,从而实现函数之间的信息传递。此外,使用对象指针作为参数仅将对象的地址传递给形参,而不进行副本的复制,可以提高运行效率,减少时空开销。

 

2.6.3 使用对象引用作为函数参数

引用相当于变量的别名,声明引用的格式:

数据类型 &引用名 = 已定义的变量名;
int &j = i;  //表示j是i的引用,j相当于i的别名。变量i和j对应同一内存单元,当i变化,j也随着变化,反之亦然。
Point &p = ob;  //ob是一个已定义的Point类的对象,对象p是对象ob的引用。

7.cpp

#include <iostream>
#include <iomanip>
using namespace std;
class Point
{
	private:
		int x,y;
	public:
		Point(int a, int b);
		void Add(Point &p);
		void Print();
};

Point::Point(int a, int b)
{
	x = a;
	y = b;
}

void Point::Print()
{
	cout << "x:" << x << setw(5) << "y:" << y <<endl;
}

void Point::Add(Point &p)
{
	p.x = p.x + 1;
	p.y = p.y + 1;
}

int main()
{
	Point ob(1,2);
	cout << "before add:";
	ob.Print();
	ob.Add(ob);
	cout << "after  add:";
	ob.Print();
	return 0;
}

 

2.6.4 三种传递方式比较

(1)使用对象作为函数参数:当进行函数调用时,需要给形参分配存储单元,形参和实参的结合是值传递,实参将自己的值传递给形参,形参实际上是实参的副本,这是一种单向传递,形参的变化不会影响到实参。

(2)使用指针作为函数参数:这种形式,实参将自己的地址传递给形参,这是一种双向传递,形参的变化会直接影响到实参。

(3)使用引用作为函数参数:当进行函数调用时,在内存中并没有产生实参的副本,它是直接对实参操作;这种方式是双向传递,形参的变化会直接影响到实参。与指针做函数参数比较,这种方式更容易使用、更清晰。而且当参数传递的数据较大时,用引用比用一般传递参数的效率和所占空间都好。

 

2.7 友元

类的主要特点之一是数据隐藏,也就是类的私有成员或保护成员只能通过它的成员函数来访问。C++中提供了友元机制来解决在类外对某个对象的私有成员或保护成员进行操作。友元可以是不属于任何类的一般函数,也可以是另一个类的成员函数,还可以是整个的一个类(这是,这个类的所有成员函数都可以成为友元函数)。

 

2.7.1 友元函数

友元函数不是当前类中的成员函数,它可以是一个不属于任何一个类的一般函数,也可以是另一个类的成员函数。当函数被声明为一个类的友元函数后,它就可以通过对象访问类的私有成员和保护成员。

非成员函数作为友元函数,其作为类的友元函数后,就可以通过对象访问封装在类内部的数据。声明友元函数:

friend 函数返回值 函数名(形参表);

8.cpp

#include <iostream>
using namespace std;

class Complex
{
	private:
		double real;
		double imag;
	public:
		Complex(double r, double i);
		void Print();
		friend Complex add(Complex c1, Complex c2);
};

Complex::Complex(double r, double i)
{
	real = r;
	imag = i;
}

void Complex::Print()
{
	cout <<real;
	if(imag > 0)
		cout << "+";
	if(imag != 0)
		cout << imag << "i" <<endl;
}

Complex add(Complex c1, Complex c2)
{
	c1.real = c1.real + c2.real;
	c1.imag = c1.imag + c2.imag;
	return c1;
}

int main()
{
	Complex com1(1.1, 2.2),com2(3.3, 4.4);
	com1 = add(com1, com2);
	com1.Print();
	return 0;
}

(1)友元函数定义的时候,不用在函数面前家“类名::”,因为它不是该类的成员函数;友元函数没有this指针;调用友元函数时必须在它的实参表中给出要访问的对象。

(2)友元函数可以在类的私有部分进行声明,也可以在类的公有部分进行声明。

(3)当一个函数需要访问多个类时,应该把这个函数同时定义为这些类的友元函数。

9.cpp

#include <iostream>
using namespace std;

class Time;
class Date
{
	private:
		int year, month, day;
	public:
		Date(int y, int m, int d);
		friend void Calcutetime(Date d, Time t);
};

class Time
{
	private:
		int hour, minute, second;
	public:
		Time(int h, int m, int s);
		friend void Calcutetime(Date d, Time t);
};

Date::Date(int y, int m, int d):year(y), month(m), day(d){}
Time::Time(int h, int m, int s):hour(h), minute(m), second(s){}
void Calcutetime(Date d, Time t)
{
	int mon[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
	int i, days = 0, totaltime;
	for(i = 1; i < d.month; i++)
		days = days+mon[i-1];
	if ((d.year%4==0 && d.year %100 != 0 || d.year % 400 == 0) && d.month >= 3)
		days = days+1;
	totaltime = ((days*24+t.hour)*60+t.minute)*60 + t.second;
	cout <<d.year << '-' << d.month << '-' << d.day << " ";
	cout << t.hour << ':' << t.minute << ':' << t.second <<endl;
	cout << "total time: " << totaltime << " seconds" <<endl;
}

int main()
{
	Date d(2004,10,18);
	Time t(14,20,25);
	Calcutetime(d,t);
	return 0;
}

如果友元函数带了两个不同类的对象,其中一个对象所对应的类要在后面声明。为了避免编译时出错,编程时必须通过向前引用告诉C++,该类将在后面定义,如果类A用到的参数类型是后面即将定义的类B,则需要在声明类A的前面进行一个空声明,即class B;,表示向前引用,类B的定义在后面。

友元函数提高了程序的运行效率,实现了类之间的数据共享,方便了编程。但是声明友元函数相当于在实现封装的黑盒子上开了一个黑洞,如果一个类声明了许多友元,则相当于在黑盒子上开了许多洞,在一定程度上破坏了数据的隐蔽性和类的封装性,降低了程序的可维护性。

类的成员函数作为友元函数

一个类的成员函数可以作为另一类的友元,这种成员函数不仅可以访问自己所在类中的成员,还可以通过对象名访问friend声明语句所在类的私有成员和保护成员,从而使两个类相互合作。

10.cpp

#include <iostream>
using namespace std;

class Time;
class Date
{
	private:
		int year, month, day;
	public:
		Date(int y, int m, int d);
		void Calcutetime(Time t);
};

class Time
{
	private:
		int hour, minute, second;
	public:
		Time(int h, int m, int s);
		friend void Date::Calcutetime(Time t);
};

Date::Date(int y, int m, int d):year(y), month(m), day(d){}
Time::Time(int h, int m, int s):hour(h), minute(m), second(s){}
void Date::Calcutetime(Time t)
{
	int mon[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
	int i, days = 0, totaltime;
	for(i = 1; i < month; i++)
		days = days+mon[i-1];
	if ((year%4==0 && year %100 != 0 || year % 400 == 0) && month >= 3)
		days = days+1;
	totaltime = ((days*24+t.hour)*60+t.minute)*60 + t.second;
	cout <<year << '-' << month << '-' << day << " ";
	cout << t.hour << ':' << t.minute << ':' << t.second <<endl;
	cout << "total time: " << totaltime << " seconds" <<endl;
}

int main()
{
	Date d(2004,10,18);
	Time t(14,20,25);
	d.Calcutetime(t);
	return 0;
}

 

2.7.2 友元类

友元函数可以使函数能够访问某个类中的私有或保护成员。如果类A的所有成员函数都想访问类B的私有或保护成员,一种方法是将类A的所有成员函数都声明为类B的友元函数,但是这样做显得比较麻烦,且程序也显得冗余。为此,C++提供了友元类。也就是一个类也可以作为另一个类的友元类。若类A声明为类B的友元类,则类A中的每个成员函数都具有访问类B的保护或私有数据成员的特权。

 11.cpp

#include <iostream>
using namespace std;

class Complex
{
	private:
		double real;
		double imag;
	public:
		Complex(double r, double i);
		friend class Vector;
};

class Vector
{
	private:
		double x,y;
	public:
		void Change(Complex c);
		void Print(Complex c);
};

void Vector::Change(Complex c)
{
	x = c.real;
	y = c.imag;
}

void Vector::Print(Complex c)
{
	cout << "fushu:";
	cout << c.real;
	if(c.imag > 0)
		cout << "+";
	cout <<c.imag << "i" <<endl;
	cout << "Vector:";
	cout << "(" << x << "," << y << ")" <<endl;
}

Complex::Complex(double r, double i)
{
	real = r;
	imag = i;
}

int main()
{
	Complex c(1.1, 2.2);
	Vector v;
	v.Change(c);
	v.Print(c);	
	return 0;
}

声明友元类就像把自家的钥匙给了另一个类,意思是你是我的朋友,我很信任你,你可以来我家,并且家里的东西你可以随便用。

注意:(1)友元关系是单向的。如果类A时类B的友元,则类A中的所有成员函数都可以直接访问类B的保护和私有数据成员,反之则不能。

(2)友元关系不能传递。如果类A时类B的友元,类B时类C的友元,不能推出类A就是类C的友元。友元在声明时既可以放在类的私有部分,也可以放在类的公有部分。

  

猜你喜欢

转载自blog.csdn.net/qq_38289815/article/details/81282946