目录
对象指针
指针不仅可以指向普通变量,也可以指向对象。
1、指向对象的指针
一个对象存储空间的起始地址就是对象的指针。换句话说,定义一个指针变量,去存放对象的地址,这就是指向对象的指针变量。其一般形式为: 类名 *对象指针名 。
Time *p; //指向 Time 类的指针变量
Time t;
p=&t; //将对象t的起始地址赋给指针p
2、指向对象成员的指针
对象中的成员也有地址,那么用来存放对象中成员地址的指针变量就是指向对象成员的指针。成员分数据成员和成员函数,故指针也有两类:
- 指向对象数据成员的指针
Time t ;
int *p ;
p=&t.hour ;
cout<<"hour="<<*p <<endl;
需要注意的是,类中的数据成员需要是 public 类型的,但一般数据成员都是 private ,所以这种指针很少会用到。
- 延:指向普通函数的指针
int (*p)() ; //定义一个指向 int 型函数的指针变量
p=max ; //将 max 函数的入口地址赋给指针变量 p ,p此时就指向了函数 max
(*p)() ; //调用函数 max
- 指向对象成员函数的指针
而指向对象成员函数的指针却不能像指向普通函数一样
p = t.show ;
因为现所需要调用的函数是属于一个类,用指针指向时需指明是属于哪个类。
void (Time:: *p)();
p=&Time::show;
定义指向公用成员函数的指针变量的一般形式为: 数据类型名(类名:: *指针变量名)(参数列表);使指针变量指向一个公用成员函数的一般形式为: 指针变量名 = & 类名:: 成员函数名 。
下面列一个例子有助于了解一下:
class Time{
int hour ;
int min ;
public:
int sec ;
Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
void show(){
cout<<hour<<":"<<min<<":"<<sec<<endl;
}
};
void display(){
cout<<" " <<endl;
}
int main(){
Time t(1,1,1);
int *p1; //指向对象数据成员的指针
p1=&t.sec;
cout<<*p1<<endl;
void (Time :: *p2)(); //指向对象成员函数的指针
p2=&Time::show ; //注意不需要后边的括号!!!
(t.*p2)();
void (*p3)(); //指向普通函数的指针
p3=&display; //注意不需要后边的括号!!!
(*p3)();
Time *p4=&t ; //指向对象的指针
p4->show();
}
注意关于指向对象的成员函数的指针,其成员函数的入口地址正确写法是 (p2=&Time::show),而不是(p2=&t.show),前面已提到过,成员函数不是存放在对象的空间中,而是在对象外的空间,如果有多个同类的对象,它们是公用的一个函数代码段。还可以简写:
void (Time :: *p2)()=&Time::show ;
(t.*p2)();
3、指向当前对象的 this 指针
在每一个成员函数中都包含有一个特殊的指针,这个指针有个固定的名字 ,称为 this ,它是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。例如,当调用成员函数 t . show() 时,编译系统就把对象 t 的起始地址赋给 this 指针,于是在成员函数引用数据成员时,就按照 this 指针的指向找到对象 t 的数据成员 。
//show()函数输出时分秒 hour:min:sec ,实际上是执行:
this->hour : this->min : this->sec
//由于当前的 this 指向 t ,故相当于执行:
a.hour : a.min : a.sec
this 指针时隐式使用的,它是作为参数被传递给成员函数。本身成员函数 show()的定义是:
void show(){
cout << hour << ":" << min << ":" << sec <<ednl;
}
由于 this 的介入,编译系统是这样处理的:
void show(Time *this){
cout << this->hour << ":" << this->min << ":" << this->sec <<endl;
}
即在成员函数的形参数表列中增加一个 this 指针 ,在调用该成员函数时,实际上是用以下的方式调用的:
t.show(&t);
这些都是编译系统自动实现的,像我们就没必要刻意地在形参中增加 this 指针 ,也不必将对象 t 的地址传给 this 指针,这就是让我们对 this 指针的作用和实现原理有更清楚的认识。当然也可以显式地使用 this 指针:
void show(){
cout << this->hour << ":" << this->min << ":" << this->sec <<endl;
}
都是正确的。也可以用 *this 表示被调用的成员函数所在的对象,*this 就是 this 所指向的对象,即当前的对象,例如在 t . show() 的函数体中,如果出现的 *this ,那么它就是指的本对象 t 。但注意:
void show(){
cout << (*this).hour << ":" <<(*this).min << ":" << (*this).sec <<endl;
}
*this 两侧的括号不能省去,因为成员运算符“ . ”的优先级高于指针运算符" * "。
经过 this 的说明,那么所谓 “调用对象 t 的成员函数 show()”,实际上是在调用成员函数 show()时使 this 指针指向对象 t ,从而访问对象 t 的成员 ,这个是对 “调用对象 t 的成员函数show()”的正确解释含义。
共用数据的保护
对于有些数据,使它既能在一定范围内共享,又要保证它不被任意修改,这时就可以把相关数据定义为常量。
1、常对象
在定义对象时前加关键字 const ,指定对象时常对象,常对象一定要有初值。
定义过常对象之后,在该对象的生命周期内,对象的所有的数据成员的值都不能被改变。定义常对象的一般形式是: 类名 const 对象名(实参表),或者将 const 放到最前边 : const 类名 对象名(实参表)。在定义对象时必须对之初始化,但如果对象所在的类中有默认构造函数就不做要求,之后对象就不能再改变。
如果一个对象被声明为常对象,则通过该对象只能调用它的常成员函数,而不准调用对象的普通成员函数(除系统自动调用的构造函数和析构函数),常成员函数是常对象唯一的对外接口,这是为了防止普通成员函数会修改对象中数据成员的值。另外常成员函数可以访问常对象中的数据成员,但不准修改常对象中数据成员的值。
以上两点(不能调用常对象中的普通成员函数和常成员函数不准改变其对象的数据成员的值)就保证了常对象中的数据成员的值绝对不会被改变。
但有时会需要修改常对象中某个数据成员的值(例如一个用于计数的变量 count ,其值需要能不断变化),考虑到实际需要,对此作了一个特殊的处理,对该数据成员声明为 mutable :
mutable int count ;
即把 count 声明为可变的数据成员,这样就可以用声明为 const 的成员函数修改它的值。
class Time{
int hour ;
int min ;
mutable int sec ;
public:
Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
void show() const{
cout << hour << ":" <<min << ":" << sec <<endl;
}
void count() const ;
};
void Time::count() const {
sec++;
}
int main(){
Time const t;
const Time t1;
t1.show();
t.count();
t.show();
}
2、常对象成员
对象的成员也可以声明为常量,包括常数据成员和常成员函数。
- 常数据成员
常数据成员的值是不能被改变的,只能通过构造函数的参数初始化表对常数据成员进行初始化,任何其他函数都不能对常数据成员赋值。
const int hour ;
Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
Time (int h=1 ,int m=0 , int s=0):hour(h){
min=m;
sec=s;
}
注意是只能通过构造函数的参数初始化表进行初始化!,可以试一下用构造函数的其他初始化方式,发现都会出错,因为其他构造函数的初始化操作仍属于对常数据成员的赋值操作。
Time(){
hour=0;
}
Time(int h){
hour=h;
}
常对象的数据成员都是常数据成员,因此在定义常对象时,构造函数只能用参数初始化表对常数据成员进行初始化。
- 常成员函数
前边已提过常成员函数只能访问本类中的数据成员,而不能进行修改。
声明常成员函数的一般形式是:类型名 函数名(参数表) const ; 。和声明常对象时不一样,const 只能放在最后(函数名和括号之后) 。另外不要忘记的是 const 是函数类型的一部分,在声明函数和定义函数时都需要有 const 的关键字,只有在调用时不需要加 const 。如下:
class Time{
int hour ;
int min ;
public:
int sec ;
Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
void show() const{
cout << hour << ":" <<min << ":" << sec <<endl;
}
void show_1() const ;
};
void Time::show_1() const {
cout << hour << ":" <<min << ":" << sec <<endl;
}
int main(){
Time const t;
const Time t1(1,1,1);
t1.show_1();
t.show();
}
下面用一个表格来使对数据成员的使用印象更直观一点:
数据成员 | 非 const 的普通成员函数 |
const 成员函数 |
---|---|---|
非 const 的普通数据成员 | 可以引用,也可以改变值 | 可以引用,但不可以改变值 |
const 数据成员 | 可以引用,但不可以改变值 | 可以引用,但不可以改变值 |
const 对象 | 不允许 | 可以引用,但不可以改变值 |
如果在一个类中,有些数据成员的值允许改变,另一些数据成员的值不允许改变,那么可以将一部分数据声明为 const ,以保证其值不被改变,用非 const 的成员函数引用这么数据成员的值,并修改非 const 数据成员的值;如果要求所有的数据的值都不允许改变,则可以将所有的数据成员声明为 const 或将对象声明为 const (常对象),然后用 const 成员函数引用数据成员,这“双保险”的作用,切实保证了数据成员不会被修改。
常对象只保证其数据成员是常数据成员,其值不会被修改,如果在常对象中的成员函数未加 const 声明,编译系统把它作为非 const 成员函数处理。另外需注意,常成员函数不能调用另一个非 const 成员函数。
3、指向对象的常指针
说的是将指针变量声明为 const 型,这样指针变量始终保持为初值,不能改变,即其指向不能改变。定义指向对象的常指针变量的一般形式为: 类名 *const 指针变量名 ; const位置在指针变量名前面。另外注意应该在定义指针变量时使之初始化。
Time t;
Time * const p=&t ;
//p=&t;
p->show();
t.show();
如果想将一个指针变量固定地与一个对象相联系(即该指针变量始终指向一个对象),可以将它指定为 const 型指针变量,这样可以防止误操作,增加安全性。
4、指向常对象的指针
- 引:指向常变量的指针
const char *p;
const 的位置在最左侧,与类型名 char 紧连,表示指针变量 p 指向的 char 变量是常变量,不能通过 p 来改变其值。
那么有指向常变量的指针变量的一般形式为: const 类型名 * 指针变量名 ;
- 如果一个变量已被声明为常变量,只能用指向常变量的指针变量指向它,而不能用一般的(指向非 const 型变量)指针变量去指向它。
const char str[] = "hello" ;
const char *p ;
p=str ; //数组名就是数组中第一个元素的地址
char *q=str ; //错误
- 指向常变量的指针除了可以指向常变量外,还可以指向未被声明为 const 的变量,此时不能通过此指针变量改变其变量的值,但如果不是通过指针访问,则变量的值是可以改变的(即能通过改变原变量的值该改变常指针目前的值)。
const int a=1;
int b=2 ;
const int *p ;
int *q;
//q=&a; 错误,无法去指
p=&b;
cout<<*p<<endl;
//*p=1; 错误,不能改变值
b=3;
cout<<*p<<endl;
q=&b;
*q=1;
cout<<*q<<endl;
注意:定义了指向常变量的指针 p 并使它指向 b ,并不意味着把 b 也声明为常变量,而只是在用指针变量访问 b 期间,b 具有常变量的特征(即其值不能改变),在其他情况下, b 仍然是一个普通的变量,其值是可以改变的。
- 如果函数的形参是指向普通(非 const )变量的指针变量,实参只能用指向普通(非 const)变量的指针,而不能用指向 const 变量的指针,这样在执行函数的过程中可以改变形参指针变量所指向的变量(也就是实参指针所指向的变量)的值。
使用形参和实参的对应关系表:
形参 | 实参 | 合法否 | 改变指针所指向的变量的值 |
---|---|---|---|
指向非 const 型变量的指针 | 非 const 变量的地址 | 合法 | 可以 |
指向非 const 型变量的指针 | const 变量的地址 | 非法 | / |
指向 const 型变量的指针 | const 变量的地址 | 合法 | 不可以 |
指向 const 型变量的指针 | 非 const 变量的地址 | 合法 | 不可以 |
可见指向常变量的指针变量可以指向 const 和 非 const 型的变量,而指向非 const 型变量的指针只能指向非 const 的变量。
指向常对象的指针变量的概念和使用与上边的很类似,只要将 “变量” 换成 “对象” 即可。
- 如果一个对象已被声明为常对象,只能用指向常对象的指针变量指向它,而不能用一般的(指向非 const 型对象)指针变量去指向它。
- 如果定义了一个指向常对象的指针变量,并使它指向一个非 const 型的对象,则其指向的对象是不能通过该指针变量去改变的(同上边一样,只能访问,改变只能是对象自身发生改变)。
Time t(1,1,1);
const Time *p;
p=&t;
//p->sec=2; 不能被改变
t.sec=2;
(*p).show(); //注意show()的类型
p->show();
注意:上边最后提到,指向常变量的指针指向非 const 型变量时,在指针访问期间,该变量具有了常变量的的特征。在此处同样,非 const 型的对象 t 在指针访问期间有的常对象的特征,而常对象只能访问其内的常成员函数,上边的show()函数应该定义成 const 型。
- 指向常对象的指针最常用于函数的形参,目的是在保护形参指针所指向的对象,使它在函数执行过程中不被修改。
void set(const Time *p){
//p->sec=1; 错误
cout<<p->sec<<endl;
}
int main(){
Time t;
set(&t);
}
如果形参不是指向 const 型 Time 对象的指针变量,即函数形参为 “ void set (Time *p)” 此时 t 的值可以在函数 set中被修改。前边已提过,如果形参是指向非 const 对象的指针变量,那么实参不能是 const 型的对象。
当希望在调用函数时对象的值不被修改,就应当把形参定义为指向常对象的指针变量,同时用对象的地址作实参(对象可以是 const 或 非 const 型);如果要求对象不仅在调用函数过程中不被改变,而且要求它在程序执行过程中都不被改变,则应把它定义为 const 型。
- 如果定义了一个指向常对象的指针变量,是不能通过它改变所指向的对象的值,但指针变量本身的值是可以改变的。
Time t1 , t2 ;
const Time *p=&t1;
p=&t2 ;
//p->sec=1; 不能更改
- 延:为什么常用指针作函数参数?举两个基础的例子:
//这是以前接触到的值的互换的函数:
void set(int *p,int *q){ //虽然这方面用引用会更简单,但这也是为了解释指针的一个方式
int r ;
r=*p ;
*p=*q ;
*q=r ;
}
int main(){
int a=1,b=2 ;
set(&a,&b);
cout<<"a="<<a<<'\t'<<"b="<<b<<endl;
}
//对象指针
void set(Time t){
t.sec=1;
}
int main(){
Time t;
set(t);
cout<<t.sec<<endl;
}
因为形参中出现了变量名 t ,是在函数调用时建立了一个新的对象 t ,是实参 t 的的一个拷贝,实参把值传给形参,二者占用不同的存储空间,所以无论形参如何变化都不会影响到实参的值。尤其是当对象(变量)的规模比较大时,时间和空间开销都可能会很大,所以常用指针作函数参数(或者用 引用 表示)。
5、对象的常引用
对象的引用和变量的引用用法都一样,也都表示起了一个别名的作用,也都共用一段存储空间。此处讲的是把引用声明为 const ,即常引用。
//对象的引用
void set(Time &t){
t.sec=1;
}
//对象的常引用
void set(const Time &t){
t.sec=1; //常引用时不能改变对象中成员的值
}
int main(){
Time t;
set(t);
t.show();
}
在程序设计中,经常用常指针和常引用用作函数参数,这样既能保证数据安全,是数据不能被任意修改,在调用函数时又不必建立实参的拷贝。后边会了解到,每次调用函数建立实参的拷贝时,都需要调用复制构造函数,有时间开销。用常指针和常引用作函数参数时,可以提高程序运行效率。
6、const 型数据小结
从最开始的常对象、常对象成员,这部分还好,认识都比较清晰,但到了指向对象的常指针、指向常对象的指针,在形式上也是能分清,但它们的功能有的都是相互交错,很容易就混淆了,下面稍微总结一下 const 的用法。
形式 | 含义 |
---|---|
Time const t ; | t 是常对象,其值在任何情况下都不能被改变。 |
void Time :: show() const ; | show是 Time 类中的常成员函数,可以引用,但不能修改本类中的数据成员。 |
Time * const p ; | p 是指向 Time 类的常指针变量,p 的值(其指向)不能改变。 |
const Time * p ; | p 是指向 Time类常对象的指针变量, p 指向的类对象的值不能通过 p 来改变。 |
const Time &t1=t ; | t1 是 Time 类的对象 t 的引用,二者指向同一存储空间,t 的值不能被改变。 |
const 类型数据是很重要的,这部分的内容比较繁琐难记,在学习时不必死记,对它有一定的了解即可,以后有不会的就再回顾一下,加深理解,慢慢就掌握了。