请选择 进入手机版 | 继续访问电脑版

C++编程

 找回密码
 立即注册

QQ登录

只需一步,快速开始

楼主: 000

000山寨的笔记

[复制链接]

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-14 15:46:36 | 显示全部楼层
之后是多继承
多继承的话其实就是在继承的基础上的一个内存顺序的变化(和声明多继承的顺序有关)
例程:
  1. #include<iostream>
  2. using namespace std;
  3. class MyClass
  4. {
  5. public:
  6.         MyClass(int, int, int, long long);
  7.         ~MyClass();

  8. private:
  9.         int m_a;
  10.         int m_b;
  11.         int m_c;
  12.         long long m_d;
  13. };
  14. class MyClass1
  15. {
  16. public:
  17.         MyClass1(long long);
  18.         ~MyClass1();

  19. private:
  20.         long long m_d;

  21. };

  22. MyClass1::MyClass1(long long d):m_d(d)
  23. {
  24. }

  25. MyClass1::~MyClass1()
  26. {
  27. }
  28. MyClass::MyClass(int a, int b, int c, long long d) :m_a(a), m_b(b), m_c(c), m_d(d)
  29. {
  30. }

  31. MyClass::~MyClass()
  32. {
  33. }
  34. class Test:public MyClass,public MyClass1
  35. {
  36. public:
  37.         Test(int, int, int, long long, long long, int);
  38.         ~Test();

  39. private:
  40.         int m_f;
  41. };

  42. Test::Test(int a,int b,int c,long long d, long long e, int f):MyClass(a,b,c,d),MyClass1(e),m_f(f)
  43. {
  44. }

  45. Test::~Test()
  46. {
  47. }
  48. int main()
  49. {
  50.         Test *test1 = new Test(1, 2, 3, 4, 5, 6);
  51.         cout << *(int *)((unsigned int)test1 + sizeof(unsigned int) * ) << endl;
  52.         delete test1;
  53.         return 0;
  54. }
复制代码
此时结果明显是5(0+4+4+4(4)+8) = int*6
那么如果在40行对那两个继承声明做替换呢?
如果还想输出5那应该是int * 0
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-14 17:37:58 | 显示全部楼层
最后是虚继承(最为繁琐和难以理解的)
虚继承的内存模型大概就是这个样子的:(图中的是错误的,应该是a1,a2,b1,b2,应为这个模型的编译器是VC的不是CL的所以无法确定正确性)
其中a是虚基类,b是a的虚继承,c是b的派生,d是c的派生,发现,对于虚基类其内存一直是在最后,如果遇到两个虚基类的派生关系的话实质同普通继承一样的,先到最底层,然后按照一直放在最后的原则,这样回来。
图中,阴影部分为共享部分(应为考虑到多继承二义性,所以共享一个达到消除二义性的目的),白色部分为固定部分,共享部分会随着构造函数的调用地址发生偏移,而固定部分不会,并且在调用继承的构造函数时,可以直接调用,并且优先调用虚基类的构造函数(如果遇到同时两个虚基类同时存在,则按照一开始的原则,由基类到派生)
-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+华丽丽的分割线,前方高能预警-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
但是,上面只是一个大概的模型,实际上,有很多的蹊跷在里面
直接上例程分析,可能会很直观:
  1. #include<iostream>
  2. using namespace std;
  3. class MyClass
  4. {
  5. public:
  6.         MyClass(int);
  7.         MyClass();
  8.         ~MyClass();

  9. private:
  10.         int m_a;
  11.         int n_b;
  12. };

  13. MyClass::MyClass(int a):m_a(a)
  14. {
  15.         cout << "Used 0" << endl;
  16. }
  17. MyClass::MyClass(){}
  18. MyClass::~MyClass()
  19. {
  20. }
  21. class MyClass1:virtual public MyClass
  22. {
  23. public:
  24.         MyClass1(int);
  25.         MyClass1();
  26.         ~MyClass1();

  27. private:
  28.         int m_b;
  29.         int n_c;
  30. };

  31. MyClass1::MyClass1(int b):m_b(b)
  32. {
  33.         cout << "Used 1" << endl;
  34. }
  35. MyClass1::MyClass1() {}
  36. MyClass1::~MyClass1()
  37. {
  38. }
  39. class MyClass2:virtual public MyClass1
  40. {
  41. public:
  42.         MyClass2(int, int, int);
  43.         ~MyClass2();

  44. private:
  45.         int m_c;
  46.         int m_d;
  47. };

  48. MyClass2::MyClass2(int a, int b, int c) :MyClass1(b), MyClass(a), m_c(c)
  49. {
  50.         cout << "Used 2" << endl;
  51. }
  52. MyClass2::~MyClass2()
  53. {
  54. }
  55. int main()
  56. {
  57.         MyClass2 *test = new MyClass2(1, 2, 3);
  58.         return 0;
  59. }
复制代码
这里再附上关键性的三个内存图:



这里在给一个大概的图:(图是错的,同样据说是针对VC编译器而言但是模型差不多)

首先第一点,A是B的虚基类,B是C的虚基类,那么
从内存上(从上到下)应该是:
point(->m_a,->point1),
m_c,m_d,
m_a,n_b,
point1(->m_b),
m_b,n_c
再由内存图可知:第一个是一个地址,这个地址过去是一个(多个)偏移量
再继续分析上面point的内容,
第一个4字节为0(猜测:标记为存在虚类)
第二个4字节为最底层虚基类的偏移量相对于point地址+12
第三个4字节为虚派生的指针地址
然后跟进,发现为0但是后面没有值,表示后面没有虚派生了
此时猜想,如果存在第三个需继承呢
经过验证,是这样的,最后那一个虚基类的成员前也会有一个指针做标记。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-16 21:55:45 | 显示全部楼层
来说说多态(毕竟是类的三大特性的最后一个了,封装,继承,多态)
多态是建立在继承关系上的知识点是:大概就是虚函数和纯虚函数了吧(一般沾到这俩家伙,多态没跑的)
首先要知道,基类可以直接指向派生类的反过来是不可以的(很类似向上转型,但是向上转型是一个地址引用的过程或者可以理解为一个地址解读的方法但是这里的指向只是单纯的指向派生类对象(实质上是无法访问任何派生类对象的成员的))
下面的例程一次性就可以把多态所有的特性描述完毕(目前我所学到的基本的内容)
例程:
  1. #include <iostream>
  2. #include <typeinfo>
  3. using namespace std;
  4. class A
  5. {
  6. public:
  7.         A(int);
  8.         A();
  9.         virtual ~A();
  10.         virtual void show() = 0;
  11.         virtual void bace() = 0;
  12. protected:
  13.         char *m_b;
  14. private:
  15.         int m_a;
  16. };
  17. A::A(int a):m_a(a)
  18. {
  19.         m_b = new char[a];
  20. }
  21. A::A(){}
  22. A::~A()
  23. {
  24.         cout << "A U" << endl;
  25.         delete this->m_b;
  26. }
  27. class B:virtual public A
  28. {
  29. public:
  30.         B(int);
  31.         virtual ~B();
  32.         void show();
  33.         virtual void temp_show();
  34. protected:
  35.         char *m_d;
  36. private:
  37.         int m_c;
  38. };
  39. B::B(int c) :m_c(c)
  40. {
  41.         A::m_b[5] = 5;
  42.         m_d = new char[c];
  43. }
  44. B::~B()
  45. {
  46.         cout << "B U" << endl;
  47.         delete this->m_d;
  48. }
  49. void B::show()
  50. {
  51.         cout << this->m_d << " is a " << this->m_c << " boy" << endl;
  52.         cout << this->m_b << " is a " << "girl" << endl;
  53. }
  54. void B::temp_show()
  55. {
  56.         cout << "this is bace B" << endl;
  57. }
  58. class C:virtual A
  59. {
  60. public:
  61.         C(int);
  62.         virtual ~C();
  63.         void bace();
  64. protected:
  65.         char *m_f;
  66. private:
  67.         int m_e;
  68. };

  69. C::C(int e):m_e(e)
  70. {
  71.         m_f = new char(m_e);
  72. }

  73. C::~C()
  74. {
  75.         delete this->m_f;
  76.         cout << "C U" << endl;
  77. }
  78. void C::bace()
  79. {
  80.         cout << "this is A bace" << endl;
  81. }
  82. class E
  83. {
  84. public:
  85.         E();
  86.         virtual ~E();
  87.         virtual void temp_show();
  88. private:

  89. };
  90. E::E()
  91. {
  92. }
  93. E::~E()
  94. {
  95.         cout << "E U" << endl;
  96. }
  97. void E::temp_show()
  98. {
  99.         cout << "this is E bace" << endl;
  100. }
  101. class D :public B, public C, public E
  102. {
  103. public:
  104.         D(int, int, int, int);
  105.         virtual ~D();
  106.         virtual void temp_show();
  107. protected:
  108.         char *m_h;
  109. private:
  110.         int m_g;
  111. };
  112. void D::temp_show()
  113. {
  114.         const type_info &temp = typeid(this);
  115.         this->show();
  116.         this->bace();
  117.         cout << "this is D bace" << endl;
  118.         cout << temp.name() << endl;
  119. }
  120. D::D(int a,int c, int e, int g):A(a),B(c),C(e),m_g(g)
  121. {
  122.         m_h = new char[m_g];
  123. }

  124. D::~D()
  125. {
  126.         delete this->m_h;
  127. }
  128. int main()
  129. {
  130.         E *exmple = new D(10, 11, 12, 13);
  131.         sscanf_s("test", "%s", *(char **)((unsigned int)exmple + sizeof(unsigned int)* (- 2)), 11);
  132.         sscanf_s("test1", "%s", *((char **)((unsigned int)(*(int *)(*(int *)((unsigned int)exmple + sizeof(unsigned int)) + sizeof(unsigned int))) + (unsigned int)exmple + sizeof(unsigned int) * 2)), 10);
  133.         exmple->temp_show();
  134.         delete exmple;
  135. }
复制代码

这个程序的输出是:test is a 11 boy
test1 is a girl
this is A bace
this is D bace
class D *
E U
C U
B U
A U

确实,在底层的输入输出上C比C++好用那么一点点。。


回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-17 13:27:02 | 显示全部楼层
上面的程序很有意思,大概的继承图:
根据这个继承关系可以发现只有D类可以被实例化,但是可以是任何基类的,但是其实内存的排布和类型无关
有一个类似优先级的东西
首先内存模型大致像:

由此就可以清晰的看出第一个输入
  1. sscanf_s("test", "%s", *(char **)((unsigned int)exmple + sizeof(unsigned int)* (- 2)), 11);
复制代码
实质上就是在储存了B类的字符串m_d里面写入test之后第二个
  1. sscanf_s("test1", "%s", *((char **)((unsigned int)(*(int *)(*(int *)((unsigned int)exmple + sizeof(unsigned int)) + sizeof(unsigned int))) + (unsigned int)exmple + sizeof(unsigned int) * 2)), 10);
复制代码
从最里层开始,我们发现(unsigned int)exmple + sizeof(unsigned int)此时就相当于对exmple加了一个指针的长度,根据上面的模型,可以知道现在的地址是一个虚基类信息指针,这个指针的值一般情况下是有两个值,第一个为0表示该指针的下一个位置,第二个是一个由当前这个指针的值的后面那个地址到下一个虚基类成员地址的大小,所以是(*(int *)(*(int *)((unsigned int)exmple + sizeof(unsigned int)) + sizeof(unsigned int))) 现对这个虚基类指针的值加一个4byte然后再解引就得到了这个偏移量,然后加上(unsigned int)exmple + sizeof(unsigned int) * 2就可以得到下一个虚基类的成员参数的地址了。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-17 13:50:13 | 显示全部楼层
这里还要注意,在上图每一个虚基类+虚继承前面都会有一个RTTI指针和一个开头为fcffffff然后后面跟了4byte大小为从当前指针位置+4+这个值的一个虚继承的地址
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-17 13:58:43 | 显示全部楼层
如:我把上面例程的两个抽象类(B和C)改为:
  1. class B:virtual public A
  2. {
  3. public:
  4.         B(int);
  5.         virtual ~B();
  6.         void show();
  7.         virtual void temp_show();
  8. protected:
  9.         char *m_d;
  10. private:
  11.         int m_c;
  12. };
  13. void B::temp_show()
  14. {
  15.         cout << "this is  bace" << endl;
  16. }
  17. B::B(int c) :m_c(c)
  18. {
  19.         A::m_b[5] = 5;
  20.         m_d = new char[c];
  21. }
  22. B::~B()
  23. {
  24.         cout << "B U" << endl;
  25.         delete this->m_d;
  26. }
  27. void B::show()
  28. {
  29.         cout << this->m_d << " is a " << this->m_c << " boy" << endl;
  30.         cout << this->m_b << " is a " << "girl" << endl;
  31. }
  32. class C:virtual A
  33. {
  34. public:
  35.         C(int);
  36.         virtual ~C();
  37.         void bace();
  38.         virtual void temp_show();
  39. protected:
  40.         char *m_f;
  41. private:
  42.         int m_e;
  43. };
  44. void C::temp_show()
  45. {
  46.         cout << "this is  bace" << endl;
  47. }
  48. C::C(int e):m_e(e)
  49. {
  50.         m_f = new char(m_e);
  51. }

  52. C::~C()
  53. {
  54.         delete this->m_f;
  55.         cout << "C U" << endl;
  56. }
  57. void C::bace()
  58. {
  59.         cout << "this is A bace" << endl;
  60. }
复制代码

此时下面的第一个输入就要为:
  1. sscanf_s("test", "%s", *(char **)((unsigned int)exmple + sizeof(unsigned int)* (- 6)), 11);
复制代码

应为exmple指针前面8byte为C类的成员变量
再前面8btye的第一组4byte为RTTI指针,第二组为地址跟踪过去以后就是:fdffffff  20 00 00 00
然后此时再往前推8byte就是B类的成员变量
此时-6就是B类中m_d指针的值。
再看下面的输入:
  1. sscanf_s("test1", "%s", *((char **)((unsigned int)(*(int *)(*(int *)((unsigned int)exmple - sizeof(unsigned int)*3) + sizeof(unsigned int))) + (unsigned int)exmple - sizeof(unsigned int) * 2)), 10);
复制代码

回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-17 14:01:22 | 显示全部楼层
这里要注意以下,是要求在还有虚继承的成员前才会有一个指针是指向虚基类指针的(两者必对应)
回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-21 10:45:14 | 显示全部楼层
终于把重载运算符看完了,也补了一些东西
重载运算符,首先是要看是否是对称运算如 + - * /
此时应该(必须)使用友元来进行运算符重载的(重载运算符实质上是对特殊函数的特殊调用)
例:
  1. #include<stdio.h>
  2. using namespace std;
  3. class Complex
  4. {
  5. public:
  6.         Complex(double);
  7.         friend double operator+(const Complex &,const Complex& );
  8.         double operator+(const Complex&);
  9. private:
  10.         double m_a;
  11. };

  12. Complex::Complex(double a = 0):m_a(a)
  13. {}
  14. double Complex::operator+(const Complex& b)
  15. {
  16.         printf("Menber Func Was Called \n");
  17.         return this->m_a + b.m_a;
  18. }
  19. double operator+(const Complex &a, const Complex &b)
  20. {
  21.         printf("Friend Func Was Called \n");
  22.         return a.m_a + b.m_a;
  23. }
  24. int main()
  25. {
  26.         Complex a = 15.3;
  27.         Complex b = 16.3;
  28.         printf("%lf\n", a + b);
  29.         printf("%lf\n", 7 + b);
  30.         printf("%lf\n", a + 7);
  31.         return 0;
  32. }
复制代码

运行结果:Menber Func Was Called
31.600000
Friend Func Was Called
23.300000
Menber Func Was Called
22.300000

我们发现,这里首选调用的是成员函数,并且就算损失一定的性能(应为在最后那个调用需要调用到转换构造函数(将double转换为对象)
之后就是一个为什么对于上面四类运算一定要用友元来声明的问题了
首先我们知道运算符重载实际上也是函数的调用
对于成员函数实际上是这样子的(用上面的对象举例)
a.operator+(b)
如果是
第二个表达式,可能会理所当然的想到
Complex(7).operator+(b);这样的
实质上编译器是不会对这个做任何转换的,所以实际上就是
7.operator+(b);明显错误,C++只会对参数进行转换,而不会对调用方做转换。
所以总结,对于一般要求对称运算的运算符都要求使用友元来声明如果对于 +=,-=等运算符的话还是保持运算符重载的初衷好(方便对象之间的运算)
并且C++规定, =,->,[],()只能以成员函数的方式来重载

回复 支持 1 反对 0

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-21 15:31:07 | 显示全部楼层
既然看到了转换构造函数,那么也来讲讲之前没有涉及到的一些基础的东西好了首先就是转换+构造函于一身的:
  1. class Complex
  2. {
  3. public:
  4.         Complex(double, double);
  5. private:
  6.         double m_a, m_b;
  7. };

  8. Complex::Complex(double a = 0, double b = 0) :m_a(a), m_b(b)
  9. {}
复制代码
这里就用了一个默认参数的方法,此时一个参数一样是可以初始化一个对象的。
然后就是复制构造函数
形式:
Complex(Complex&);
这里注意,一定要是引用
应为在函数对象传参,非应用返回对象的时候都会调用这个复制函数,想想,在传参的时候调用了这个函数,如果不是引用,那么意味着这还必须再调用一次,这时就会出现死循环的情况
这既然讲到了返回值的话,那么久扩展开来讲讲对象返回的时候的情形:
都知道,临时变量在函数结束的时候都会出栈销毁,那么返回的是一个临时对象呢?
答案是当然也会
例程:
  1. #include<iostream>
  2. using namespace std;
  3. class Complex
  4. {
  5. public:
  6.         Complex(const Complex&);
  7.         Complex(int);
  8.         friend ostream& operator<<(ostream &, const Complex);
  9.         friend Complex operator+(const Complex &, const Complex &);
  10. private:
  11.         int m_a;
  12. };
  13. Complex::Complex(int i = 0) :m_a(i)
  14. {

  15. }
  16. Complex::Complex(const Complex &a)
  17. {
  18.         this->m_a = a.m_a;
  19.         cout << "Copy" << endl;
  20. }
  21. Complex operator+(const Complex &a, const Complex &b)
  22. {
  23.         Complex c(a.m_a + b.m_a);
  24.         return c;
  25. }
  26. ostream& operator<<(ostream &out, const Complex a)
  27. {
  28.         out << a.m_a;
  29.         return out;
  30. }
  31. int main()
  32. {
  33.         Complex a = 1, b = 2;
  34.         cout << a + b << endl;
  35.         return 0;
  36. }
复制代码

输出结果:
Copy
3

可以发现,第三个Copy是在返回值的时候是在返回值的时候,应为返回的是一个临时变量,所以系统会调用一次复制构造函数来创建一个匿名的对象来储存即将被释放的对象的内容(拷贝生成的对象在函数的返回区),并用做返回值。
这里有一个机制,就是命名返回值优化
这个东西简言之就是在调用函数的时候直接就是在调用函数的返回区直接进行对象的建立,这个时候就省去了一个对象销毁的过程。
之后就是明白定义,这里要求的是建立和返回是一个对象,那么我们也可以直接建立一个并返回,同时可以达到这个返回值优化(匿名的)
例如:
  1. MyClass MyMethod(int a, int b)  
  2. {  
  3.     MyClass tmp1(a, b);  
  4.     MyClass tmp2(b, a);  
  5.     if(a > b)  
  6.     {  
  7.         return tmp1;  
  8.     }  
  9.     else  
  10.     {  
  11.         return tmp2;  
  12.     }  
  13. }  
复制代码
这个是没有办法的,应为这里要返回的经历了创建tmp1与tmp2,并根据条件返回某个tmp的过程,不具备前文所述NRVO的条件(所有返回语句都要返回一个相同对象)
可优化为
  1. MyClass MyMethod(int a, int b)  
  2. {  
  3.     if(a > b)  
  4.     {  
  5.         return MyClass(a, b);  
  6.     }  
  7.     else  
  8.     {  
  9.         return MyClass(b, a);  
  10.     }  
  11. }  
复制代码
其实这些都在:
https://msdn.microsoft.com/zh-cn/library/ms364057.aspx
里面有介绍到。

回复 支持 反对

使用道具 举报

6

主题

62

帖子

206

积分

初软

Rank: 3Rank: 3

积分
206
 楼主| 发表于 2017-4-21 22:51:24 | 显示全部楼层
既然说到了运算符重载的话,那么来谈谈为啥必须以成员函数重载 = -> [] ()这四个符号
首先我们知道用了 = 的情况很简单,赋值和复制都可以调用它
但是可以注意到,其实 = 运算符是在类中默认存在的
(这个等于号如果是相同对象与相同对象之间的话就类似于一个拷贝函数,如果是一个数或者其他什么的话他就会对类声明中第一个成员参数进行赋值)
那么如果我们重载了一个全局的 = 运算的话,就会造成二义性,这是自然不可能的
之后是->运算符,这个运算符我们都知道是用于当作指针指向的当然其实和上面的说法一样的都是二义性问题
总结过来其实就是,当类中没有定义赋值运算符重载成员函数时(注意,在未定义形参数据类型为该类类型的赋值运算符重载函数时,编译器会自动生成加入),当程序执行到某一赋值语 句时,程序就会调用与赋值语句中右值类型匹配的构造函数,而把这右值当作此构造函数的实参。像最初的赋值语句a = 7,执行时,实际做的操作是a(7)。而当类中有定义赋值运算符重载成员函数,执行赋值语句时,程序就只会去调用相应的赋值运算符重载函数。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|C++编程  

GMT+8, 2019-7-22 19:39 , Processed in 0.093750 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表