参考资料:《21天学通C++》

对象

对象概述

对象是现实世界中的实体,其一般具有以下特征:

  • 每个对象都有一个用于与其他对象相区别的名字。
  • 具有某些特征,称为属性或状态。
  • 有一组操作,每一个操作决定对象的一种行为,即对象能干什么。
  • 对象的状态只能被自身行为所改变。
  • 对象之间以消息传递的方式相互通信。

注意:上述特征在面向对象程序设计中分别被抽象为对象名属性事件方法消息。这是面向对象程序设计的基础。

对象数组

对象数组是指以数组元素为对象的数组,该数组中若干个元素必须是同一个类的若干对象。对象数组的定义、赋值和引用与普通数组一样,只是数组的元素与普通数组不同,它是同类的若干个对象。

一般来说,定义对象数组的格式如下:

1
类名 数组名[大小]

其中参数说明如下:

  • 类名:指出该数组元素是属于该类的对象。
  • 数组名:定义该对象数组的名称,它必须是C++的标识符。
  • 大小:方括号内的数字给出某一维的元素个数。一维对象数组只有一个方括号,二维对象数组要有两个方括号等。

例如,下列语句定义了一个一维对象数组:

1
Cbook dates[7];

上述语句表明dates是一维对象数组名,该数组有7个元素,其中的对象属于Cbook类,也即每个元素都是类Cbook的对象。

与普通数组类似,对象数组可以被赋初值,也可以被赋值。

例1 对象数组的定义和赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

class DATE { //声明类
public: //公有成员
DATE(int m, int d, int y);
void print();
private: //私有成员
int month, day, year;
};

DATE dates[4] = {DATE(7, 7, 2001), DATE(7, 8, 2001), DATE(7, 9, 2001), DATE(7, 10, 2001)}; //初始化

DATE :: DATE(int m, int d, int y) { //定义成员函数(构造函数)
month = m;
day = d;
year = y;
}

void DATE :: print() { //定义成员函数
cout << month << "\t" << day << "\t" << year << endl;
}

void main() {
int i;
for(i = 0; i < 4; i++)
dates[i].print(); //调用对象的print函数
}

输出:

1
2
3
4
7       7       2001
7 8 2001
7 9 2001
7 10 2001

构造函数

构造函数的概念

注意:在类中定义成员变量时,不能给这些变量赋初值。

构造函数是在类中定义的一种特殊的函数,其函数名称与类的名称相同。

构造函数的主要功能是为对象分配空间,也可以用来为类成员变量赋初值,因此构造函数没有返回类型,也没有return语句。

构造函数具有以下性质:

  • 构造函数的名字必须与类的名字相同
  • 构造函数的参数可以是任何数据类型,但它没有返回值,不能为它定义返回类型。
  • 对象定义时,编译系统自动调用构造函数完成对象内存空间的分配和初始化工作。
  • 构造函数是类的成员函数,具有一般成员函数的所有性质,可以访问类的所有成员,可以是内联函数,可以带参数表,可以有默认的形参值,也可以重载。

构造函数的声明和定义

构造函数的声明和定义与普通成员函数的声明和定义类似。

例如,下面的语句给出了一个类的声明和定义,其中包含了构造函数的声明和定义:

1
2
3
4
5
6
7
8
9
10
11
12
class complex {
private:
double real, imag; //定义复数的实部和虚部
public:
complex(double r, double i) { //声明构造函数,其名字与类名相同
real = r;
imag = I; //初始化私有数据成员imag和real
}
void disp() {
cout << real << "+" << imag << "i" << endl;
}
};

上面的语句定义了一个类complex,其包含两个私有成员变量,在公有成员中,其包含了两个成员函数:complex()函数和disp()函数。其中,complex函数为构造函数,其函数名与类名相同,并且带有两个参数。

此外,构造函数可以在类中声明,在外部定义。

例如,上述上明定义的类可以改写成以下形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
class complex {
private:
double real, imag; //定义复数的实部和虚部
public:
complex(double r, double i); //声明构造函数
void disp() {
cout << real << "+" << imag << "i" << endl;
}
};
complex :: complex(double r, double i) { //定义构造函数
real = r;
imag = I;
}

注意:在类的外部定义构造函数时,需要在函数前增加类名和域运算符。

一般都需要给类定义构造函数,如果没有定义,编译系统就会自动生成一个默认的构造函数,这个默认的构造函数不带任何参数,只能给对象开辟一个存储空间,不能为对象赋初值。

构造函数的调用

在定义了构造函数之后就可以对其进行调用了。一般来说,在定义对象的同时会自动调用构造函数,不需要显式调用。构造函数的调用格式为:

1
类名 对象名(实参表)

例7 构造函数的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

class complex {
private:
double real, imag; //定义复数的实部和虚部
public:
complex(double r, double i); //声明构造函数
void disp() { //声明成员函数
cout << real << "+" << imag << "i" << endl;
}
};

complex :: complex(double r, double i) { //定义构造函数
real = r; //成员变量初始化
imag = i;
}

int main(void) {
complex op1(1.5, 3.0); //创建对象
op1.disp();
return 0; //调用类的成员函数
}

输出:

1
1.5+3i

上述代码中,首先定义了类complex及其构造函数,在类外定义了该构造函数。在main()函数中,没有直接调用构造函数,而是在声明了对象op1的同时系统自动调用了该构造函数。

不带参数的构造函数

构造函数也可以不带参数。

例8 不带参数的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

class myclass { //定义类
private: //定义私有成员
int a;
public:
myclass(); //声明不带参数的构造函数
void disp() {
cout << "a * a = " << a << endl;
}
};

myclass :: myclass() { //定义构造函数
cout << "initialized.\n";
a = 10; //给变量初始化
}

int main(void) {
myclass s; //创建对象,执行构造函数
s.disp(); //调用成员函数
return 0;
}

输出:

1
2
initialized.
a * a = 10

上述代码中,定义了一个类myclass,在类中声明了该类的构造函数myclass()。该构造函数无参数,在类外定义该构造函数,输出提示信息“initialized”并给私有变量赋初值0。

注意:不带参数的构造函数的初始化是固定的,如果希望在建立对象时通过参数初始化数据成员,应使用带参数的构造函数。

带有默认参数的构造函数

在实际应用中,有些构造函数的参数值通常是不变的,只有在特殊情况下才改变它的值,这时可以将构造函数定义成带默认参数的值的构造函数。此时在定义对象时可以不指定实参,用默认参数值来初始化数据成员。

例9 带默认参数的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <math.h>
using namespace std;

class complex {
private:
double real, imag;
public:
complex(double real = 0.0, double imag = 0.0); //声明带有默认参数的构造函数
double abscomplex();
};

complex :: complex(double r, double i) { //定义构造函数
real = r; //成员初始化
imag = i;
}

double complex :: abscomplex() { //定义成员函数
double n;
n = real * real + imag * imag;
return sqrt(n); //返回平方根值
}

int main(void) {
complex ob1; //创建对象ob1
complex ob2(1.1); //创建对象ob2
complex ob3(1.1, 2.2); //创建对象ob3

cout << "abs of complex ob1 = " << ob1.abscomplex() << endl;
cout << "abs of complex ob2 = " << ob2.abscomplex() << endl;
cout << "abs of complex ob3 = " << ob3.abscomplex() << endl;

return 0;
}

输出:

1
2
3
abs of complex ob1 = 0
abs of complex ob2 = 1.1
abs of complex ob3 = 2.45967

上述代码中,对象ob1在定义时没有传递任何参数,所以real和imag均去构造函数的默认值初始化,real和imag均为0.0。对象ob2在定义时传递了一个参数,按顺序传递给了第一个形参real,第二个形参imag取默认值,所以real为1.1,imag为0.0。对象ob3在定义时传递了两个参数,所以real为1.1,imag为2.2。

注意:创建对象调用的构造函数是否带参数,其创建的对象对成员变量的初始化程度是不一样的。

构造函数的重载

C++允许对构造函数的重载,即可以定义多个参数及参数类型不同的构造函数,用多种方法对对象初始化。

例10 构造函数的重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
using namespace std;

class point {
private:
double fx, fy;
public:
point(); //声明不带参数的构造函数
point(double x, double y); //声明带两个参数的构造函数
void showpoint();
};

point :: point() { //定义不带参数的构造函数
fx = 0.0; //成员初始化
fy = 0.0;
}

point :: point(double x, double y = 5.5) { //定义带两个参数的构造函数
fx = x; //成员初始化
fy = y;
}

void point :: showpoint() { //定义成员函数
cout << fx << "\t" << fy << endl;
}

int main(void) {
point p1; //用构造函数point创建对象
cout << "The fx and fy of p1: ";
p1.showpoint();

point p2(1.0); //用构造函数point(double x, double y = 5.5)创建对象
cout << "The fx and fy of p2: "; //fy用默认值5.5初始化
p2.showpoint();

point p3(1.1, 2.0);
cout << "The fx and fy of p3: "; //用构造函数point(double x, double y = 5.5)创建对象
p3.showpoint(); //fy被重新赋值为2.0

return 0;
}

输出:

1
2
3
The fx and fy of p1: 0    0
The fx and fy of p2: 1 5.5
The fx and fy of p3: 1.1 2

上述代码中,类point中声明了两个构造函数:point()和point(double x, double y)。在main()函数中声明对象时,系统自动调用相应的构造函数,如在point p1;语句中声明p1对象时,调用的是point()构造函数。

拷贝构造函数

拷贝构造函数的作用是用一个已经存在的对象来初始化该类的新对象,用户可以根据需要定义拷贝构造函数,也可以由系统生成一个默认的拷贝构造函数。

定义拷贝构造函数

定义拷贝构造函数的形式为:

1
2
3
类名(类名 &对象名) {
函数体
}

其中对象名是用来初始化另一个对象的引用。

例1 自定义拷贝构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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;
}

输出:

1
2
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 调用拷贝构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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;
}

输出:

1
2
3
4
5
6
调用拷贝构造函数
12 24
调用拷贝构造函数
22 44
调用拷贝构造函数
13 25

上述代码中,定义了类point和拷贝构造函数point(point &p),在main()函数中首先用构造函数声明了对象p1,接着分别采用上述三种形式调用拷贝构造函数声明了p2,并输出了x和y的值。

默认拷贝构造函数

当用一个已经存在的对象初始化本类的新对象时,如果没有自定义拷贝构造函数,则系统会自动生成一个默认的拷贝构造函数来完成初始化工作。

析构函数

析构函数也是一类特殊的成员函数,也被声明为公有成员。析构函数作用时释放分配给对象的内存空间,并做一些“善后”工作。析构函数在声明定义和使用时需要注意:

  • 析构函数的名字必须与类名相同,但在名字前要加波浪号“~”
  • 析构函数无参数、无返回值、不能重载,在一个类中只能有一个析构函数
  • 撤销对象时,系统自动调用析构函数完成空间的释放和“善后”工作

例3 析构函数的声明和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#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;
}

输出:

1
2
3
constructing
abs of complex ob = 2.45967
destructing

上述代码中,定义了一个类complex,在类中声明了该类的构造函数和析构函数,在类外定义了构造函数和析构函数。在main()函数中创建对象ob,调用构造函数,因此输出“constructing”的信息。程序结束后编译系统自动调用析构函数释放该对象,因此输出“destructing”的信息。

注意:

  • 每一个类必须有一个析构函数,若没有显式定义,则系统会自动生成一个默认的析构函数,它是一个空函数
  • 对于大多数类而言,默认的析构函数就可以满足需求,但如果对象在完成操作前需要做内部处理,则应该显式地定义析构函数
  • 构造函数和析构函数最常见的用法是,在构造函数中用new运算符为对象分配空间,在析构函数中用delete运算符释放空间

友元

友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。

如果友元是一般成员函数或类的成员函数,则称为友元函数;如果友元是一个类,则称为友元类,友元类的所有成员函数都是友元函数。

友元函数

友元函数不是当前类的成员函数,而是独立于当前类的外部函数,它可以是普通函数或其他类的成员函数。友元函数定以后可以访问该类的所有对象成员。

友元函数在使用前必须要在类定义时声明,声明时在其函数名前加上关键字friend,该声明可以放在公有成员中,也可以放在私有成员中。

友元函数的定义可以在类的内部进行,也可以在类的外部进行,但通常都放在类的外部。

例4 普通函数作为友元函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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;
}

输出:

1
The distance is: 5

上述代码定义了类point,在类中声明了友元函数dist(),在类的外部对该函数进行了定义。

注意:

  • 友元函数不是成员函数,所以定义时不必加域运算符
  • 友元函数不是成员函数,所以不能直接引用对象成员的名字,也不能通过this指针引用对象的成员,必须通过作为入口的参数传递进来的对象名或对象指针来引用该对象的成员。为此,友元函数一般都有一个该类的入口参数,如dist(point &p1, point &p2)。
  • 当一个函数需要访问多个类时,应该把这个函数同时定义为这些类的友元函数

友元成员

如果一个类的成员函数是另一个类的友元函数,则称这个成员函数为友元成员。通过友元成员函数,不仅可以访问自己所在类对象中的私有和共有成员,还可以访问由关键字friend声明语句所在的类对象中的私有成员,从而使两个类可以互相访问。

例5 友元成员的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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);
}

输出:

1
2
girl's name: Stacy age: 15
boy's name: Jim age: 16

上述代码中定义了boy和girl两个类,在类boy中声明了girl类的prt()函数为其友元成员,在类外定义了该成员函数。该函数既可以访问girl类的成员变量age,又可以访问boy类的成员age。

注意:当一个类的成员函数作为另一个类的友元函数时,必须先定义成员函数所在的类,并且在声明友元函数时,要加上成员函数所在的类名和域运算符,如第33行。另外,在主函数中要创建一个类girl的对象。

友元类

当一个类作为另一个类的友元时,称这个类为友元类

当一个类成为另一个类的友元类时,这个类的所有成员函数都称为另一个类的友元函数。

C++中,友元类的声明可以放在类声明中的任何位置,这时,友元类中的所有成员函数都称为友元函数。

友元类声明的一般形式如下:

1
2
3
friend class 友元类名;

friend 友元类名;

例6 友元类的应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#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);
}

输出:

1
2
girl's name: Stacy age: 15
boy's name: Jim age: 16

上述代码同样定义了boy类和girl类,此处在boy类的定义中将girl类声明为boy类的友元类,因此,girl类的成员可以访问boy类的任意成员和函数。

注意:友元关系是不能传递的,并且是单向的。

最后更新: 2019年04月16日 17:18

原始链接: qnkcdz0.xyz/2019/04/16/C-学习笔记之类和对象(下)/