关于同名成员变量
直接来看下面子类和父类有同名变量的使用情况,代码如下:
#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就访问失败。
不会因为子类的成员访问不了,就取访问基类的同名成员。
关于成员变量和成员函数,都是适用的。