关于动态内存分配:
new Test共完成两步:
1、申请堆空间
2、在申请的堆空间上调用构造函数
示例程序:
第18行申请了内存空间,然后在申请的内存空间上调用了构造函数。
而第19行仅仅是申请了内存空间。
面向对象中尽量使用new。
new是C++语言的一部分。
delete:
第25行的delete执行了析构函数,并且释放了内存空间。
第26行仅仅释放了内存空间。
new申请的内存要用delete释放,千万不要用free释放。因为用free释放时仅仅会释放内存,但是不会执行析构函数。
delete一个对象时会先调用析构函数。
malloc申请的内存,也不要用delete释放。有可能产生bug。
关于虚函数:
在构造函数执行结束之后,虚函数表指针才会被正确初始化,因此,执行构造函数时虚函数表指针还是随机值,所以,构造函数不能成为虚函数。
析构函数在对象销毁之前被调用,因此,析构函数是可以成为虚函数的。
实验:
父类和子类的析构函数都没有定义成虚函数。delete指针p时只调用了父类的析构函数,没有调用子类的析构函数。
当析构函数没有声明为虚函数时,编译器直接根据p的类型来决定调用哪个析构函数。
将析构函数声明为虚函数,执行结果如下:
这时编译器会根据p指针指向的实际类型来决定调用哪些析构函数,可以看到先调用子类的析构函数又调用父类的析构函数。
在工程中将一个类作为父类时,习惯性的将析构函数声明为虚函数。否则有可能 产生内存泄漏,因为有可能子类的析构函数调用不到。
在构造函数中调用虚函数或者在析构函数中调用虚函数,编译器怎么处理呢?
在这两种情况下调用的虚函数必然是当前类中定义的版本。
实验:
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class Base 7 { 8 public: 9 Base() 10 { 11 cout << "Base()" << endl; 12 13 func(); 14 } 15 16 virtual void func() 17 { 18 cout << "Base::func()" << endl; 19 } 20 21 virtual ~Base() 22 { 23 func(); 24 25 cout << "~Base()" << endl; 26 } 27 }; 28 29 30 class Derived : public Base 31 { 32 public: 33 Derived() 34 { 35 cout << "Derived()" << endl; 36 37 func(); 38 } 39 40 virtual void func() 41 { 42 cout << "Derived::func()" << endl; 43 } 44 45 ~Derived() 46 { 47 func(); 48 49 cout << "~Derived()" << endl; 50 } 51 }; 52 53 54 int main() 55 { 56 Base* p = new Derived(); 57 58 // ... 59 60 delete p; 61 62 return 0; 63 }
结果如下:
由结果可以看出,13、23行的func函数都是调用的当前类中的版本。子类中的调用类似。
关于继承中的强制类型转换:
继承中如何正确的使用强制类型转换?
示例:
第29行的使用是错误的。
因为编译器会检查dynamic_cast的使用是否合适。29行的使用是不合适的。
因为dynamic_cast要求相关的类中必须有虚函数。
我们无需另外定义一个虚函数,只需把析构函数声明为虚函数即可。
再次编译就可以通过了。
示例程序:
可以看到转换不成功时返回0。
dynamic_cast就是为C++中的面向对象程序设计而诞生的。
小结: