第三十六课 c++3 权限控制
1.定义和实现分开写
2.private和public
private权限说明
类内函数访问
3.private真的不能访问吗
反汇编看看
t对象在初始化public和private成员时都是一视同仁的,在底层还是没区别,都是编译器给的限制
4.class和struct的区别
class默认都是private权限,struct默认都是public权限
class继承的一致默认所有都是private,父类public的成员继承过来也变成private权限
如果class Sub:public Base
,那么也只能访问父类的public成员,private成员还是不行
子类把父类的private继承过去了,在同一个内存里,但是它只能在那摆着,编译器不允许访问,但是从底层调用指针可以访问
第三十七课 c++4 虚函数
1.虚函数:间接调用
#include <stdio.h>
class Base
{
public:
void Function_1()
{
printf("Function_1...\n");
}
virtual void Function_2()
{
printf("Function_2...\n");
}
};
int main()
{
Base base;
base.Function_1();
base.Function_2();
}
总结:
2.深入虚函数调用
我们可以发现在类中,如果使用了虚函数,不管使用了几个虚函数都只会多四个字节,我们现在需要探索一下这四个字节是什么
反汇编看一下间接调用了什么
调用两个虚函数时,他call edx+4
就是第二个虚函数的地址
总结:
3.打印虚函数表
#include <stdio.h>
class Base
{
public:
int x;
int y;
virtual void Function_1()
{
printf("Function_1...\n");
}
virtual void Function_2()
{
printf("Function_2...\n");
}
virtual void Function_3()
{
printf("Function_3...\n");
}
};
int main()
{
//查看 Sub 的虚函数表
Base base;
//对象的前四个字节就是虚函数表
printf("base 的虚函数表地址为:%x\n", *(int*)&base);
//通过函数指针调用函数,验证正确性
typedef void(*pFunction)(void);
pFunction pFn;
for (int i = 0; i < 3; i++)
{
int temp = *((int*)(*(int*)&base) + i);
pFn = (pFunction)temp;
pFn();
}
}
注意:
-
&base就是对象的首地址
-
(int)&base才是虚函数的地址
-
((int)((int)&base))解引用出第一个虚函数的地址,然后使用函数指针去调用这个虚函数地址**
-
((int)((int)&base) + i)解引用出第i个虚函数的地址
总结:
作业
1、单继承无函数覆盖
2、单继承有函数覆盖
输出后程序会崩溃,因为虚函数表只有4个函数地址,我打印了六个,第五个开始内存都是0,调用不了
3、多继承无函数覆盖
(多个直接父类时有多张虚表且子类虚函数在第一个虚表里)
struct Base1
{
public:
virtual void Fn_1()
{
printf("Base1:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base1:Fn_2...\n");
}
};
struct Base2
{
public:
virtual void Fn_3()
{
printf("Base2:Fn_3...\n");
}
virtual void Fn_4()
{
printf("Base2:Fn_4...\n");
}
};
struct Sub:Base1,Base2
{
public:
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
virtual void Fn_6()
{
printf("Sub:Fn_6...\n");
}
};
int main(int argc, char* argv[])
{
//查看 Sub 的虚函数表
Sub sub;
//通过函数指针调用函数,验证正确性
typedef void(*pFunction)(void);
//对象的前四个字节是第一个Base1的虚表
printf("Sub 的虚函数表地址为:%x\n",*(int*)&sub);
pFunction pFn;
for(int i=0;i<6;i++)
{
int temp = *((int*)(*(int*)&sub)+i);
if(temp == 0)
{
break;
}
pFn = (pFunction)temp;
pFn();
}
//对象的第二个四字节是Base2的虚表
printf("Sub 的虚函数表地址为:%x\n",*(int*)((int)&sub+4));
pFunction pFn1;
for(int k=0;k<2;k++)
{
int temp = *((int*)(*(int*)((int)&sub+4))+k);
pFn1 = (pFunction)temp;
pFn1();
}
}
4、多继承有函数覆盖
两个父类的虚函数先放两张表里,然后子类虚函数覆盖上去
struct Base1
{
public:
virtual void Fn_1()
{
printf("Base1:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base1:Fn_2...\n");
}
};
struct Base2
{
public:
virtual void Fn_3()
{
printf("Base2:Fn_3...\n");
}
virtual void Fn_4()
{
printf("Base2:Fn_4...\n");
}
};
struct Sub:Base1,Base2
{
public:
virtual void Fn_1()
{
printf("Sub:Fn_1...\n");
}
virtual void Fn_3()
{
printf("Sub:Fn_3...\n");
}
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
};
5、多重继承无函数覆盖
6、多重继承有函数覆盖
后来的覆盖前面的
struct Base1
{
public:
virtual void Fn_1()
{
printf("Base1:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base1:Fn_2...\n");
}
};
struct Base2:Base1
{
public:
virtual void Fn_1()
{
printf("Base2:Fn_1...\n");
}
virtual void Fn_3()
{
printf("Base2:Fn_3...\n");
}
};
struct Sub:Base2
{
public:
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
};
struct Base1
{
public:
virtual void Fn_1()
{
printf("Base1:Fn_1...\n");
}
virtual void Fn_2()
{
printf("Base1:Fn_2...\n");
}
};
struct Base2:Base1
{
public:
virtual void Fn_1()
{
printf("Base2:Fn_1...\n");
}
virtual void Fn_3()
{
printf("Base2:Fn_3...\n");
}
};
struct Sub:Base2
{
public:
virtual void Fn_1()
{
printf("Sub:Fn_1...\n");
}
virtual void Fn_5()
{
printf("Sub:Fn_5...\n");
}
};
第三十七课 c++5 多态-绑定
1.什么是绑定
编译器绑定(前期绑定)
变量x和funciton_1就属于编译器绑定,一编译完调用普通成员变量的地方的变量地址已经确定了
动态绑定(后期绑定)
function_2就是动态绑定,编译完调用函数的地方还没确定地址,这里只是call虚表第一个函数中的值,只有运行时确定虚表第一个值是多少
多态例子一:
class Base
{
public:
int x;
public:
Base()
{
x = 100;
}
void Function_1()
{
printf("Base:Function_1...\n");
}
virtual void Function_2()
{
printf("Base:Function_2...virtual\n");
}
};
class Sub:public Base
{
public:
int x;
public:
Sub()
{
x = 200;
}
void Function_1()
{
printf("Sub:Function_1...\n");
}
virtual void Function_2()
{
printf("Sub:Function_2...virtual\n");
}
};
void TestBound(Base* pb)
{
int n = pb->x;
printf("%x\n",n);
pb->Function_1(); //函数调用
pb->Function_2();
}
int main(int argc, char* argv[])
{
return 0;
}
注意:
一直想不懂为什么传入sub对象时pb->x
依然是100?
- 因为是子类对象传入父类指针,现在子类对象继承了父类的变量,这就涉及到继承的父子类变量重名问题,子类对象里面照样把重名的变量继承过来,所以现在子类对象里有两个x变量的值,但是我们现在用父类指针去访问,所以访问出父类的变量x
多态例子二:(父类指针数组指向多个子类对象)
class Base
{
public:
int x;
int y;
public:
Base()
{
x = 1;
y = 2;
}
virtual void print()
{
printf("Base:%d %d \n",x,y);
}
};
class Sub1 :public Base
{
public:
int A;
public:
Sub1()
{
x = 3;
y = 4;
A = 7;
}
virtual void print()
{
printf("Sub1: %d %d %d\n", x, y,A);
}
};
class Sub2 :public Base
{
public:
int B;
public:
Sub2()
{
x = 5;
y = 6;
B = 8;
}
virtual void print()
{
printf("Sub2:%d %d %d\n", x, y,B);
}
};
void TestBound()
{
/*int n = pb->x;
printf("%x\n", n);*/
Base base;
Sub1 sub1;
Sub2 sub2;
Base* arr[] = { &base,&sub1,&sub2 };
for (int i = 0; i < 3; i++)
{
arr[i]->print();
}
}
int main(int argc, char* argv[])
{
TestBound();
return 0;
}
注意(重点):
问题一:为什么只输出两个数?(上面代码还没加virtual虚函数时)
因为我们没有使用虚函数,一直调用的是父类函数,父类函数我们只输出了x和y
问题二:修改后为什么这里会输出sub1和sub2各自的xy变量,例子一不是说父类指针调用父类的xy吗?
在两个sub对象中,由于我们没有重新声明x和y变量,所以在sub对象内存空间里只有x,y,A三个变量,而且在sub的构造函数中给x和y重新赋值了。然而在例子一中,我们在父子类都声明了变量x和y,所以子类内存空间里会有五个变量,(x,y,x,y,A),而后用父类指针访问自然就是打印了父类的变量x和y。
下面是两种情况的内存情况