对象指针

与c语言类似,c++可以通过使用指向对象的指针变量来访问对象及对象的公共成员
定义和用法与结构体指针相似,也是数据类型加指针名的形式,如:

class Clock{public:void ShowTime();};
Clock clock,*p=&clock;p->ShowTime();

this指针

c++为每个非静态成员函数都提供了一个关键字this,代表一个指针,this指针是一个隐含的指针常量,指向被成员函数操作的那个对象,即当前类变量(对象)的首地址
this的值具有特殊的含义,总是指向当前调用对象,因此值不能修改,只能用在类的内部,通过this可以访问类的所有访问属性的成员,this虽然用在类的内部,但是只有在对象被创建以后才会给this指针赋值,并且这个赋值的过程是编译器自动完成的,不需要用户干预,用户也不能显式地给this指针赋值。
在构造函数中使用this指针会存在问题,应该尽量避免,因为通常this指针在对象构造完毕后才完全生成,而在构造函数执行过程中,对象还没有完全生成,所以this指针也是没有完全生成的

当成员函数的参数和成员变量重名,只能通过this区分,例:

class Counter{int value;void SetValue(int value);}
Counter counter;counter SetValue(10);

此例中数据成员和成员函数的形参(局部变量)同名,因此在调用成员函数时其内部操作的value变量是成员函数内的局部变量,而不是对象的value成员,所以在SetValue()中可以使用this->value,来对类的value成员和成员函数SetValue()中的value变量加以区分
(或者干脆不要让成员函数的变量与类的成员数据同名,比如使用SetValue(int v)😉

this是关键字,不能声明与this同名的变量
this不能显示声明,还因为它是非静态成员函数的一个形参,在声明成员函数时会被隐含地调用,如:

void SetValue(Counter *this,int value); 编译器做了特殊处理,隐含地加上了名为this的形参
SetValue(&counter,10); 在调用时隐含地将对象的首地址作为第一个参数传递,因此编译器可以通过this指针的值找到对象

this指针示例程序:

#include <iostream>
using namespace std;
class Student{
public:
    void setname(char *name);
    void setage(int age);
    void setscore(float score);
    void show();
private:
    char *name;
    int age;
    float score;
};
void Student::setname(char *name){
    this->name=name;
}
void Student::setage(int age){
    this->age=age;
}
void Student::setscore(float score){
    this->score=score;
}
void Student::show(){
    cout<`<this->`name<<"的年龄是"<`<this->`age<<",成绩是"<`<this->`score<<endl;
}
int main(){
    Student *pstu = new Student;
    pstu->setname("李华");
    pstu->setage(16);
    pstu->setscore(96.5);
    pstu->show();              输出结果:李华的年龄是16,成绩是96.5
    return 0;
}

由于this指针的存在,出现了一个衍生的问题,那就是对象中成员函数(以下默认是非静态成员)的引用问题。类和对象有一个特性很有意思,那就是对于数据成员来说它们分布于每个对象实例当中,由对象来维护;但是对于成员函数来说,实际上是所有对象共享类的一组操作,因此是由类本身来维护的,并在使用时通过this指针来与调用对象进行绑定。因此,但需要引用一个对象中的成员函数时,此时这个函数指针的this指针相当于已经绑定了(因为this指针的初始化是在对象构造完毕后就进行的,不是在调用时才赋值),因此这this所指向的成员函数指针实际上已经带有了对象的信息,与单纯的函数指针的语义是不同的。如果将对象成员函数指针向普通函数指针一样使用的话,编译器报错,此时要使用类的成员函数指针必须采取以下格式:

typedef 返回值 (类名::*指针类型名)(参数列表); 声明一个类的成员函数指针类型
指针类型名 指针名 = &类名::成员函数名; 使用类的成员函数对类的成员函数指针进行赋值(必须加&,否则编译器会认为是在这里调用成员函数)
(类对象.*指针名)(参数列表); 对象调用成员函数指针所指向的成员函数
(类指针->*指针名)(参数列表);

静态成员函数属于类本身,没有this指针,因此直接将其看作普通的函数指针也没问题。

对象数组

与其他数据类型一样,可以创建一个类的对象数组,如:

Clock clock[n];

通过下标访问数组中的对象,进而访问该对象的公有数据成员,如:

Clock[i].ShowTime();

对于对象数组而言,如果没有显式定义,则自动调用该类的构造函数进行初始化
对象数组的初始化实际就是调用构造函数对每个元素进行初始化的过程,同理在销毁时也要挨个调用析构函数
如果在声明数组时给出每一个数组元素的初始值,在初始化过程中就会调用最匹配的构造函数,如:

class Point{public:float x,y;Point(){x=0,y=0;},Point(float a){x=a,y=0;},Point(float a,float b){x=a,y=b;}};
Point point[3]={Point(5.5),Point(6.5,7.5)};

其中Point(5.5)按Point(float a);定义,Point(6.5,7.5)按Point(float a,float b)定义,第三个元素未显式定义的按照最佳匹配Point()进行定义

同样的可以通过对象指针对数组元素进行快速访问,如:

Point *p=point;p++; 每次移动一个对象长度的空间

对象引用

对象引用,就是给某类对象定义一个引用,其实质是通过将被引用对象的地址赋给引用对象,使二者指向同一内存空间
这样引用对象就成了被引用对象的别名
对象引用的定义和用法与结构体变量引用的定义相同,如:

Clock &cr=clock;cr.ShowTime() 定义了clock的对象引用,并使用对象的成员

对象引用通常用作函数的参数及返回值,不仅具有对象指针的优点,而且比对象指针更简洁,方便,直观

类作为函数参数

常见的有三种方式:(跟结构体作参数的情况类似)

  • 对象本身作为参数
    • 这种情况一般自定义一个拷贝构造函数,以免出现未知错误
    • c++采用传值的方式传递参数,因此使用对象本身作为参数时,形参是实参的一个拷贝,相当于重新创建了一个对象,并且如果要作为返回值的话还要在创建一个\
    • 因此类对象作为函数参数往往需要调用拷贝构造函数,时间空间开销都比较大,效率极低
  • 对象引用作为函数参数
    • c++比较推荐的一种方式,比对象本身或对象指针做参数都容易理解和使用,同时没有任何副作用
  • 对象指针作为函数参数
    • 对象指针指向实参对象,通过间接方式访问和修改指向的对象,实际就是在访问和修改实参对象

对象指针是三种方式里面效率最高的,因为在节省空间的同时还可以进行一些地址方面的操作,比引用更灵活

三种复合类型做函数参数方式的示例程序:

#include<iostream>
#include<cstring>
using namespace std;
class Counter
{
private:
    int value;
public:
    Counter(int v)
    {
        value=v;
    }
    void add(int v)
    {
        value+=v;
    }
    void show( )
    {
        cout<<value<<endl;
    }
};
Counter func1(Counter &obj)
{
    obj.add(6);
    return obj;
}
Counter func2(Counter *obj)
{
    obj->add(6);
    return *obj;
}
int main( )
{
    Counter b1=5;
    Counter b2=func1(b1);
    Counter &b3=b1,*b4=&b1;
    func1(b2);
    func1(b3);
    func2(b4);
}