摘要

介绍了C++中复杂形式的类与对象,包括对象数组、对象指针、this指针、拷贝构造函数、向函数传递对象的方法,以及常类型(常引用、常对象、常成员函数、常数据成员)的使用和注意事项。

目录

[TOC]

对象数组与对象指针

对象数组

所谓对象数组就是每一数组元素都是对象的数组。

定义一个一维数组的格式如下:
类名 数组名[下标表达式];

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<iostream>
class exam
{
public:
void set_x(int n){ x=n; }
int get_x(){ return x; }
private:
int x;
};
main()
{
exam ob[4]; //对象数组
int i;
for (i=0;i<4;i++) ob[i].set_x(i);
for (i=0;i<4;i++) cout<<ob[i].get_x()<<′ ′;
cout<<endl;
return 0;
}

对象指针

用指针访问单个对象成员

声明对象指针的一般语法形式为:
类名* 对象指针名
当用指向对象的指针来访问对象成员时,要用->操作符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 对象指针的使用
#include<iostream>
class exe
{
public:
void set_a(int a){ x=a; }
void show_a(){ cout<<x<<endl; }
private:
int x;
};
main()
{
exe ob,*p; // 声明类exe的对象ob和类exe的对象指针p
ob.set_a(2);
ob.show_a(); // 利用对象名访问对象的成员
p=&ob; // 将对象ob的地址赋给对象指针p
p->show_a(); // 利用对象指针访问对象的成员
return 0;
}

用对象指针访问对象数组

将上面程序改写为:

1
2
3
4
5
6
7
8
9
10
11
int main()
{
exe ob[2],*p;
ob[0].set_a(10);
ob[1].set_a(20);
p=ob;
p->show_a();
p++;
p->show_a();
return 0;
}

this指针

在 C++ 中,this 指针是一个特殊的指针,它指向当前对象的实例。

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。

this是一个隐藏的指针,可以在类的成员函数中使用,它可以用来指向调用对象。

当一个对象的成员函数被调用时,编译器会隐式地传递该对象的地址作为 this 指针。

友元函数没有 this 指针,因为友元不是类的成员,只有成员函数才有 this 指针。

下面的实例有助于更好地理解 this 指针的概念:

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
#include <iostream>

class MyClass
{
private:
int value;

public:
void setValue(int value)
{
this->value = value;
}

void printValue()
{
std::cout << "Value: " << this->value << std::endl;
}
};

int main()
{
MyClass obj;
obj.setValue(42);
obj.printValue();

return 0;
}

显示this指针的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
class A{
public:
A(int x1){ x=x1; }
void disp()
{
cout << "\nthis=" << this << "when x=" << this->x;
}
private:
int x;
};
int main()
{
A a(1),b(2),c(3);
a.disp();
b.disp();
c.disp();
return 0;
}

拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象。
  • 复制对象把它作为参数传递给函数。
  • 复制对象,并从函数返回这个对象。

如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。

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
// 调用缺省的拷贝构造函数。
#include<iostream>
using namespace std;
class Coord
{
public:
Coord(int a,int b)
{
x=a;
y=b;
cout<<"Using normal constructor\n";
}
void print()
{
cout<<x<<" "<<y<<endl;
}
private:
int x,y;
};
int main()
{ Coord p1(30,40); // 定义类Coord的对象p1,
// 调用了普通构造函数初始化对象p1
Coord p2(p1); // 以“代入”法调用缺省的拷贝构造函数,
// 用对象p1初始化对象p2
Coord p3=p1; // 以“赋值”法调用缺省的拷贝构造函数,
// 用对象p1初始化对象p3
p1.print(); p2.print(); p3.print();
return 0;
}

调用拷贝构造函数的三种情况:

  1. 用类的一个对象初始化另一个对象时

    1
    2
    3
    4
    Coord p2(p1);        // 以“代入”法调用缺省的拷贝构造函数,
    // 用对象p1初始化对象p2
    Coord p3=p1; // 以“赋值”法调用缺省的拷贝构造函数,
    // 用对象p1初始化对象p3
  1. 形参是对象,实现形参和实参的结合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    fun1(Coord p)      // 函数的形参是类的对象
    {
    p.print();
    }
    int main()
    {
    Coord p1(10,20);
    fun1(p1); // 当调用函数,进行形参和实参
    结合时,调用拷贝构造函数
    return 0;
    }
  2. 返回值是对象时

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Coord fun2()
    {
    Coord p1(10,30);
    return p1; // 函数的返回值是对象
    }
    main()
    {
    Coord p2;
    p2=fun2(); // 函数执行完成,返回调用者时,
    调用拷贝构造函数
    return 0;
    }

向函数传递对象

使用对象作为函数参数

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
#include<iostream>
using namespace std;
class aClass
{
public:
aClass(int n) { i=n; }
void set(int n) { i=n; }
int get( ) { return i; }
private:
int i;
};
void sqr(aClass ob)
{
ob.set(ob.get()*ob.get());
cout<<"copy of obj has i value of ";
cout<<ob.get()<<"\n";
}
int main()
{
aClass obj(10);
sqr(obj);
cout<<"But, obj.i is unchanged in main:";
cout<<obj.get( );
return 0;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;
class aClass
{
public:
aClass(int n) { i=n; }
void set(int n){ i=n; }
int get(){ return i;}
private:
int i;
};
void sqr(aClass *ob)
{
ob->set(ob->get() * ob->get());
cout<<"Copy of obj has i value of ";
cout<<ob->get()<<"\n";
}
int main()
{
aClass obj(10);
sqr(&obj); // &指针取地址
cout<<"Now, obj.i in main() has been changed :";
cout<<obj.get() <<"\n"; return 0;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream.h>
class aClass
{
public:
aClass(int n) { i=n; }
void set(int n) { i=n; }
int get() { return i;}
private:
int i;
};
void sqr(aClass& ob)
{
ob.set(ob.get() * ob.get());
cout<<"Copy of obj has i value of ";
cout<<ob.get()<<"\n";
}
int main()
{
aClass obj(10);
sqr(obj);
cout<<"Now, obj.i in main() has been changed :";
cout<<obj.get() <<"\n";
return 0;
}

常类型

常引用

用const声明的引用就是常引用。

常引用所引用的对象不能被更改,经常见到的是常引用作为函数的形参,这样不会发生对实参的误修改。

常引用的声明形式为:const 类型说明符 &引用名

常引用作为函数形参的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "iostream"

using namespace std;

// 常引用作为函数形参,保证了不会对实参的值进行误修改,常引用的格式是: const 类型名 &引用名;
void fun(const double &d);

int main()
{
double d = 3.14;
fun(d);
return 0;
}

void fun(const double &d)
{
// 常引用作形参,在函数中不能更新d所引用的对象
double i = 6.66;
// d = i; 此处将报错!!!
cout << "d = " << d << endl;
}

常对象

常对象是指数据成员在它的生存期内不会被改变。

定义常对象时必须对其进行初始化,并且不能改变其数据成员的值。

常对象的声明形式为:类名 const 对象名 或者 const 类名 对象名。

常对象的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 常对象
class A
{
public:
A(int i, int j){
i = x;
j = y;
}
private:
int x;
int y;
};
int main()
{
A const a(1, 2); // 等价于const A a(1, 2);a是常对象,不能被更新!!!
return 0;
}
  • 如果程序中出现对常对象的数据成员试图进行修改的语句,编译器会报错。一般修改对象的数据成员有两种途径,一种是通过对象名访问公有数据成员并修改其值,而常对象的数据成员是不能被修改的;另一种是类的成员函数修改数据成员的值,而常对象不能调用普通的成员函数。可是这样的话,常对象就只剩数据,没有对外的接口了,这就需要为常对象专门定义的常成员函数了。

类的常成员函数

类中用const声明的成员函数就是常成员函数,常成员函数的声明形式为:类型说明符 函数名(参数表) const;

常成员函数需要注意的几点:

  • 常成员函数在声明和实现时都要带const关键字;
  • 常成员函数不能修改对象的数据成员,也不能访问类中没有用const声明的非常成员函数;
  • 常对象只能调用它的常成员函数,不能调用其他的普通成员函数;
  • const关键字可以被用于参与对重载函数的区分。比如,如果有两个这样声明的函数:void fun(); void fun() const;,则它们是重载函数。

常成员函数实例如下:

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
// 常成员函数
class R
{
public:
R(int i1, int i2){
R1 = i1;
R2 = i2;
}
void Print();
void Print() const; // 常成员函数
private:
int R1;
int R2;
};

void R::Print()
{
cout << "R1 = " << R1 << " , R2 = " << R2 << endl;
}

void R::Print() const{
cout << "R1 = " << R1 << " , R2 = " << R2 << endl;
}

int main()
{
R r(5, 4);
r.Print(); // 调用的是普通成员函数void Print()
R const rr(20, 45); // rr是一个常对象
rr.Print(); // 常对象只能调用常成员函数,所以此处调用的是void Print() const;
return 0;
}

常数据成员

类的数据成员也可以是常量和常引用,用const声明的数据成员就是常数据成员。

在任何函数中都不能对常数据成员赋值。构造函数对常数据成员初始化,只能通过初始化列表

。常数据成员实例如下:

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
// 常数据成员
class B{
public:
B(int i);
void Print();
const int &r; // 常引用r是对变量a的引用
private:
const int a; // 常数据成员
static const int b; // 静态常数据成员,必须在类外进行初始化!!!
};
// 静态常数据成员初始化
const int B::b = 330;
// 类外进行构造函数定义
B::B(int i): a(i), r(a){
cout << "B的对象的构造函数\n";
}

void B::Print(){
cout << "a = " << a << " , b = " << b << " , r = " << r << endl;
}


int main(){
//建立对象b1和b2,并以50和550作为初值,分别调用构造函数,通过构造函数的初始化列表给对象的常数据成员赋初值
B b1(50);
B b2(550);
b1.Print();
b2.Print();
return 0;
}