参考资料:《21天学通C++》
拷贝构造函数
拷贝构造函数的作用是用一个已经存在的对象来初始化该类的新对象,用户可以根据需要定义拷贝构造函数,也可以由系统生成一个默认的拷贝构造函数。
定义拷贝构造函数
定义拷贝构造函数的形式为:
类名(类名 &对象名) {
函数体
}
其中对象名是用来初始化另一个对象的引用。
例1 自定义拷贝构造函数
#include <iostream>
using namespace std;
class point {
private:
double fx, fy;
public:
point(point &p); //声明拷贝构造函数
point(double x, double y);
void showpoint();
};
point :: point(point &p) { //定义拷贝构造函数
fx = p.fx + 10; //定义拷贝构造函数的功能
fy = p.fy + 20;
}
point :: point(double x, double y) { //定义构造函数
fx = x;
fy = y;
}
void point :: showpoint() {
cout << fx << "\t" << fy << endl;
}
int main(void) {
point p1(1.1, 2.2); //用构造函数创建对象p1
cout << "The fx and fy of p1: ";
p1.showpoint();
point p2(p1); //用拷贝构造函数创建对象p2
cout << "The fx and fy of p2: ";
p2.showpoint();
return 0;
}
输出:
The fx and fy of p1: 1.1 2.2
The fx and fy of p2: 11.1 22.2
上述代码中,在第8行声明了一个拷贝构造函数,在第13~16行定义了该拷贝构造函数的功能。在main()函数中,使用了语句point p2(p1);语句创建了对象p2,其调用的是拷贝构造函数point(point &p)。
调用拷贝构造函数
一般来说,以下三种情况拷贝构造函数会被调用:
- 用类的对象去初始化类另一个对象时
- 函数的形参是类的对象,调用函数进行形参和实参结合时
- 函数的返回值是类的对象,函数执行完返回调用者时
例2 调用拷贝构造函数
#include <iostream>
using namespace std;
class point {
private:
int x, y;
public:
point(int a = 0, int b = 0) {
x = a;
y = b;
}
point(point &p); //声明拷贝构造函数
int getx() {
return x; //取成员变量x的值
}
int gety() {
return y; //取成员变量y的值
}
};
point :: point(point &p) { //定义拷贝构造函数
x = p.x + 10; //定义拷贝构造函数的功能
y = p.y + 20;
cout << "调用拷贝构造函数" << endl;
}
void f(point p) {
cout << p.getx() << "\t" << p.gety() << endl;
}
point g() {
point q(3, 5); //调用拷贝构造函数
return q;
}
int main(void) {
point p1(2, 4); //用构造函数创建对象p1
point p2(p1);
cout << p2.getx() << "\t" << p2.gety() << endl;
f(p2);
p2 = g();
cout << p2.getx() << "\t" << p2.gety() << endl;
return 0;
}
输出:
调用拷贝构造函数
12 24
调用拷贝构造函数
22 44
调用拷贝构造函数
13 25
上述代码中,定义了类point和拷贝构造函数point(point &p),在main()函数中首先用构造函数声明了对象p1,接着分别采用上述三种形式调用拷贝构造函数声明了p2,并输出了x和y的值。
默认拷贝构造函数
当用一个已经存在的对象初始化本类的新对象时,如果没有自定义拷贝构造函数,则系统会自动生成一个默认的拷贝构造函数来完成初始化工作。
析构函数
析构函数也是一类特殊的成员函数,也被声明为公有成员。析构函数作用时释放分配给对象的内存空间,并做一些“善后”工作。析构函数在声明定义和使用时需要注意:
- 析构函数的名字必须与类名相同,但在名字前要加波浪号“~”
- 析构函数无参数、无返回值、不能重载,在一个类中只能有一个析构函数
- 撤销对象时,系统自动调用析构函数完成空间的释放和“善后”工作
例3 析构函数的声明和使用
#include <iostream>
#include <math.h>
using namespace std;
class complex {
private:
double real, imag;
public:
complex(double real = 0.0, double imag = 0.0);
~complex(); //声明析构函数
double abscomplex();
};
complex :: complex(double r, double i) {
cout << "constructing" << endl;
real = r;
imag = i;
}
complex :: ~complex() { //定义析构函数
cout << "destructing" << endl;
}
double complex :: abscomplex() {
double n;
n = real * real + imag * imag;
return sqrt(n);
}
int main(void) {
complex ob(1.1, 2.2);
cout << "abs of complex ob = " << ob.abscomplex() << endl;
return 0;
}
输出:
constructing
abs of complex ob = 2.45967
destructing
上述代码中,定义了一个类complex,在类中声明了该类的构造函数和析构函数,在类外定义了构造函数和析构函数。在main()函数中创建对象ob,调用构造函数,因此输出“constructing”的信息。程序结束后编译系统自动调用析构函数释放该对象,因此输出“destructing”的信息。
注意:
- 每一个类必须有一个析构函数,若没有显式定义,则系统会自动生成一个默认的析构函数,它是一个空函数
- 对于大多数类而言,默认的析构函数就可以满足需求,但如果对象在完成操作前需要做内部处理,则应该显式地定义析构函数
- 构造函数和析构函数最常见的用法是,在构造函数中用new运算符为对象分配空间,在析构函数中用delete运算符释放空间
友元
友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。
如果友元是一般成员函数或类的成员函数,则称为友元函数;如果友元是一个类,则称为友元类,友元类的所有成员函数都是友元函数。
友元函数
友元函数不是当前类的成员函数,而是独立于当前类的外部函数,它可以是普通函数或其他类的成员函数。友元函数定以后可以访问该类的所有对象成员。
友元函数在使用前必须要在类定义时声明,声明时在其函数名前加上关键字friend,该声明可以放在公有成员中,也可以放在私有成员中。
友元函数的定义可以在类的内部进行,也可以在类的外部进行,但通常都放在类的外部。
例4 普通函数作为友元函数
#include <iostream>
#include <math.h>
using namespace std;
class point {
private:
double x, y;
public:
point(double a = 0, double b = 0) {
x = a;
y = b;
}
point(point &p); //重载构造函数
double getx() {
return x;
}
double gety() {
return y;
}
friend double dist(point &p1, point &p2); //声明友元函数
};
double dist(point &p1, point &p2) { //定义友元函数
return (sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)));
}
int main(void) {
point ob1(1, 1);
point ob2(4, 5);
cout << "The distance is: " << dist(ob1, ob2) << endl; //调用友元函数
return 0;
}
输出:
The distance is: 5
上述代码定义了类point,在类中声明了友元函数dist(),在类的外部对该函数进行了定义。
注意:
- 友元函数不是成员函数,所以定义时不必加域运算符
- 友元函数不是成员函数,所以不能直接引用对象成员的名字,也不能通过this指针引用对象的成员,必须通过作为入口的参数传递进来的对象名或对象指针来引用该对象的成员。为此,友元函数一般都有一个该类的入口参数,如dist(point &p1, point &p2)。
- 当一个函数需要访问多个类时,应该把这个函数同时定义为这些类的友元函数
友元成员
如果一个类的成员函数是另一个类的友元函数,则称这个成员函数为友元成员。通过友元成员函数,不仅可以访问自己所在类对象中的私有和共有成员,还可以访问由关键字friend声明语句所在的类对象中的私有成员,从而使两个类可以互相访问。
例5 友元成员的应用
#include <iostream>
#include <string.h>
using namespace std;
class boy; //声明类boy
class girl { //定义类girl
private:
char *name;
int age;
public:
girl(char *n, int a) {
name = new char[strlen(n) + 1];
strcpy(name, n);
age = a;
}
void prt(boy &); //声明公有成员函数
~girl() { //定义析构函数
delete name; //释放空间
}
};
class boy { //定义类boy
private:
char *name;
int age;
public:
boy(char *n, int a) {
name = new char[strlen(n) + 1];
strcpy(name, n);
age = a;
}
friend void girl :: prt(boy &); //声明友元成员
~boy() {
delete name;
}
};
void girl :: prt(boy &b) { //定义友元成员
cout << "girl\'s name: " << name << " age: " << age << endl;
cout << "boy\'s name: " << b.name << " age: " << b.age << endl;
}
int main(void) {
girl g("Stacy", 15);
boy b1("Jim", 16);
g.prt(b1);
}
输出:
girl's name: Stacy age: 15
boy's name: Jim age: 16
上述代码中定义了boy和girl两个类,在类boy中声明了girl类的prt()函数为其友元成员,在类外定义了该成员函数。该函数既可以访问girl类的成员变量age,又可以访问boy类的成员age。
注意:当一个类的成员函数作为另一个类的友元函数时,必须先定义成员函数所在的类,并且在声明友元函数时,要加上成员函数所在的类名和域运算符,如第33行。另外,在主函数中要创建一个类girl的对象。
友元类
当一个类作为另一个类的友元时,称这个类为友元类。
当一个类成为另一个类的友元类时,这个类的所有成员函数都称为另一个类的友元函数。
C++中,友元类的声明可以放在类声明中的任何位置,这时,友元类中的所有成员函数都称为友元函数。
友元类声明的一般形式如下:
friend class 友元类名;
或
friend 友元类名;
例6 友元类的应用
#include <iostream>
#include <string.h>
using namespace std;
class boy; //声明类boy
class girl { //定义类girl
private:
char *name;
int age;
public:
girl(char *n, int a) {
name = new char[strlen(n) + 1];
strcpy(name, n);
age = a;
}
void prt(boy &); //声明公有成员函数
~girl() { //定义析构函数
delete name; //释放空间
}
};
class boy { //定义类boy
private:
char *name;
int age;
friend girl;
public:
boy(char *n, int a) {
name = new char[strlen(n) + 1];
strcpy(name, n);
age = a;
}
friend void girl :: prt(boy &); //声明友元成员
~boy() {
delete name;
}
};
void girl :: prt(boy &b) { //定义友元成员
cout << "girl\'s name: " << name << " age: " << age << endl;
cout << "boy\'s name: " << b.name << " age: " << b.age << endl;
}
int main(void) {
girl g("Stacy", 15);
boy b1("Jim", 16);
g.prt(b1);
}
输出:
girl's name: Stacy age: 15
boy's name: Jim age: 16
上述代码同样定义了boy类和girl类,此处在boy类的定义中将girl类声明为boy类的友元类,因此,girl类的成员可以访问boy类的任意成员和函数。
注意:友元关系是不能传递的,并且是单向的。