编程参考 - 访问基类和子类中的同名变量或函数

本文详细解析了C++中同名成员变量与函数的访问规则,包括成员变量的作用域、成员函数的覆盖与重载以及虚函数的调用规则。并通过多个示例代码展示了不同情况下的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于同名成员变量

直接来看下面子类和父类有同名变量的使用情况,代码如下:

#include <stdio.h>

class A

{

    public:

        A(){}

    protected:

        int mynumber = 1;

    public:

        int getNumber(){return mynumber;}

};

class B: public A

{

    public:

        B(){}

    protected:

        int mynumber = 2;

};

int main ()

{

    B b;

    printf("B Num:%d.\n", b.getNumber());

    A a;

    printf("A Num:%d.\n", a.getNumber());

    return 0;

}

如上,A是B的基类,A和B都有一个mynumber成员变量,分别使用子类和基类的对象来调用基类的函数返回此变量,显示的都是基类的结果。

$ g++ -o test testCPP.cpp

$ ./test

B Num:1.

A Num:1.

如果分别调用基类和子类的函数,访问的成员变量就是各自类之中的成员变量。代码如下:

#include <stdio.h>

class A

{

    public:

        A(){}

    protected:

        int mynumber = 1;

    public:

        int getNumber_A(){return mynumber;}

};

class B: public A

{

    public:

        B(){}

    protected:

        int mynumber = 2;

    public:

        int getNumber_B(){return mynumber;}

};

int main ()

{

    B b;

    printf("B Num:%d.\n", b.getNumber_B());

    A a;

    printf("A Num:%d.\n", a.getNumber_A());

    return 0;

}

$ g++ -o test testCPP.cpp

$ ./test

B Num:2.

A Num:1.

在子类中使用基类的域作用符来访问基类的变量,返回的就是基类的值。在基类中是没法访问子类成员的。代码如下:

#include <stdio.h>

class A

{

    public:

        A(){}

    protected:

        int mynumber = 1;

    public:

        int getNumber_A(){return mynumber;}

};

class B: public A

{

    public:

        B(){}

    protected:

        int mynumber = 2;

    public:

        int getNumberA_B(){return A::mynumber;}

};

int main ()

{

    B b;

    printf("B Num:%d.\n", b.getNumberA_B());

    A a;

    printf("A Num:%d.\n", a.getNumber_A());

    return 0;

}

$ g++ -o test testCPP.cpp

$ ./test

B Num:1.

A Num:1.

使用父类指针来访问同名变量,代码如下:

#include <stdio.h>

class A

{

    public:

        A(){}

        int mynumber = 1;

};

class B: public A

{

    public:

        B(){}

        int mynumber = 2;

};

int main ()

{

    B b;

    A* a;

    a = &b;

    printf("Num b.mynumber = %d, a->mynumber = %d.\n", b.mynumber, a->mynumber);

    return 0;

}

$ g++ -o test testCPP.cpp

$ ./test

Num b.mynumber = 2, a->mynumber = 1.

根据调用类型来决定使用哪个类的成员变量。

在实际编程中,这样的情况是应该尽量避免的。基类子类有重名变量,容易引起歧义,在引用时一不小心就引用错了。

变量没有重载或覆盖的说法,虽然同名,但两个成员变量是同时存在的,区别在于其作用域不一样。

如果是private,那作用域是各自类内部;

如果是protected,则作用域有重叠,基类的变量是基类和子类都可访问,子类的变量子类自己可访问而基类无法访问;

如果是public,在protected的基础上加上外部访问。

对于多层继承,以此类推:比如3层继承,都有同名变量的话,下层的子类都可以访问上层基类的成员变量,而上层的基类没法访问下层子类的成员变量。

因为作用域有重叠,所以在子类中访问基类的同名成员变量时,前面要加上域作用符,即类名。

否则,在子类中直接使用变量名,则编译器找的就是当前类中的成员变量,找不到才会间接的去基类中寻找。

在使用函数访问变量时,取决于被调用函数属于哪个类,在哪个类调用就优先访问当前类的成员变量。因为执行这个被调用函数时,仅知道当前类型信息,使用的对象指针类型也只能是当前类型,而和调用对象类型无关了。也就是说,如果是子类对象调用父类函数,会转换成父类类型,来访问父类的成员。

关于同名成员函数

如果子类和父类存在同名同参数列表的成员函数,就是子类的成员函数覆盖了父类的函数。

如果类(也包括父类)中定义了同名函数,但参数列表不同,就是函数的重载。

(函数返回类型不影响函数签名,C中函数名不同则是不同函数,C++中函数名或参数列表不同就是不同函数)

如果是覆盖关系,调用对象是子类对象,就调用子类的成员函数,调用对象是父类,就调用父类的成员函数。

在成员函数里调用则优先访问当前类的成员变量或成员函数。

这种规则和控制,在编译期间就确定并完成了。

如果是重载,在C++中视为不同的函数,就是直接调用。

如果是虚函数,则按照虚函数调用规则来。

=== 分割线 ===

对同名成员变量的补充说明

总结一下,对于C++中的类的成员变量的访问,在编译期期间,分两步:

第一步,检查这个成员变量是否在当前类存在。

如果不存在,则去父类寻找是否存在。

第二步,检查当前的访问方式和变量声明访问修饰符 (private、protected、public)是否匹配。

用代码体验一下:

#include <cstdio>

class A

{

public:

  int var;

};

class B: public A

{

private:

  int var;

};

int main()

{

  B b;

  b.var; // compile error, because B have a "var" member variable, but it's private.

  return 0;

}

类B的成员是私有的,即使父类有全局访问的同名成员,也不能访问。

如果子类没有这个成员,就要访问父类的,根据父类的成员变量修饰符来决定是否能访问,代码如下:

#include <cstdio>

class A

{

public:

  int var1;

protected:

  int var2;

};

class B: public A

{

};

int main()

{

  B b;

  b.var1;

  b.var2; // compile error, can't access a protected member from external

  return 0;

}

上面例子的结果,当前类没有的成员,按照父类的访问规则来。

还要说明一下,对于第一步,检查当前的调用对象是否包含这个成员变量,如果不强制指定类型,是根据调用对象的声明类型来判断,而不会考虑对象定义时的类型。举例如下:

#include <cstdio>

class A

{

public:

  A():var(1){}

  int var;

};

class B: public A

{

public:

  B():var(2){}

  int var;

};

int main()

{

  B b;

  printf("var=%d.\n", b.var);

  A *a;

  a = &b;

  printf("var=%d.\n", a->var);

  return 0;

}

Output:

var=2.

var=1.

这里,同一个对象,如果用A类型调用,就是父类A里的变量,如果使用B类型调用,就是子类B里的变量。

如果使用成员函数的话,调用关系是一样的。因为调用时编译器知道的信息就是这个调用类型,也就是声明类型,自然使用这个类型。

但这里要注意的是,这里是把子类类型指针赋值给父类指针,然后作为父类类型来使用,这是C++支持的语言特性。赋值后,父类类型可以正常使用,可以正常访问成员变量和成员函数。

能够使用子类指针转成父类指针来访问父类成员变量, 是和类的内存实例的layout有关的。

而当使用虚函数时,则是根据实际对象类型来决定调用的成员函数。代码如下:

#include <cstdio>

class A

{

public:

  A():var(1){}

  int var;

  virtual int getVar(){return var;}

};

class B: public A

{

public:

  B():var(2){}

  int var;

  virtual int getVar(){return var;}

};

int main()

{

  B b;

  A a;

  A *p;

  p = &a;

  printf("var=%d.\n", p->getVar());

  p = &b;

  printf("var=%d.\n", p->getVar());

  return 0;

}

output:

var=1.

var=2.

关于类成员的修饰符

在类定义中,public、private、protected是访问修饰符。

访问修饰符和判断类成员是否存在无关。

类似上面的例子,B继承A,B和A有同名的变量,但B里是private的,A里是public的。

访问类B的对象的这个变量,因为存在所以尝试访问,但是private就访问失败。

不会因为子类的成员访问不了,就取访问基类的同名成员。

关于成员变量和成员函数,都是适用的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夜流冰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值