C++回顾(未完待续,长期更新)
OOP之类和对象
1. this指针的引入
每个成员函数都有一个额外的隐含的形参,这个参数就是this指针,它指向调用对象的地址。默认情况下,this的类型是指向类类型非常量版本的常量指针。可以表示成如下伪代码形式:
/* 假设现在有一个类Sales_data,以及其非常量Sales_data类型对象,则该隐式的this指针可以写成如下伪代码形式 */ Sales_data *const this = &total;
this指针一般用于解决重名问题和返回自身的值或者引用。例如:
struct A{ int a; void test(int a){ this->a = a; } };
test函数的形参a和类成员a成名,根据就近原则,直接使用a,调用的是形参a,那么如何使用被屏蔽的成员a呢,这里就是采用this指针。
2. const成员函数
紧随参数列表之后的const关键字作用为:修改隐式this指针所指向的对象的类型,如下:
/* 假设现在有一个类Sales_data,以及Sales_data类型对象,则在const成员函数中隐式的this指针可以写成如下伪代码形式 */ const Sales_data *const this = &total;
这里加const的含义是,这个函数不能修改本对象,其实就是函数体内不得对类的成员进行修改。const主要起到保护的作用。
注意:非const对象可以调用const成员函数,也可以调用非const成员函数,但是const对象只能调用const成员函数。并且,非const对象优先调用非const成员函数。
最后记住:构造函数不能为const。如果为const,怎么完成初始化工作?!
3. const成员函数和非const成员函数可以构成重载。
到此为止,构成函数重载的要素有:类的名称、函数名、函数形参表以及成员函数的const属性。事实上,函数签名就是由这几个部分构成。
在这里我们解释一个问题: 为什么C语言里面没有函数重载? 因为在编译器编译C程序时会维护一张符号表,C语言在记载函数的时候就是简单的记录函数的名字,所以函数名就是C函数的唯一标识。当我们试图定义两个名字相同的函数时,就发生了重定义。
C++是怎么做的呢? 很显然,对于普通函数,它的符号(唯一标识)是根据函数名和参数列表生成的,对于类的成员函数,还要加上类名和const属性,所以我们进行函数重载的时候,这些函数在符号表中的标识是不相同的。 C++正是通过这种机制实现了函数的重载。
注意:C++编译器生成函数符号的时候没有考虑返回值,这也是函数重载和返回值无关的原因。
4. 构造函数之构造函数初始值列表(constructor initialize list)
构造函数有一个特殊的地方,就是它可以包含一个构造函数初始化列表,如下:
Person(int id, const string &name, int age) :_id(id), _name(name), _age(age){ }
虽然以下形式,也完全可以达到目的:
Person(int id, const string &name, int age){ _id = id; _name = name; _age = age; }
但两者是不同的。第一种形式带构造函数初始值列表,执行的是真正的初始化工作;而第二种形式,进行的是赋值操作。
注意,即使构造函数没有构造函数初始值列表(更确切的说是构造函数初始值列表为空),那么类中的成员变量将会执行默认初始化。因此在以下情况我们必须使用构造函数默认初始化列表:
a)const内置类型变量以及没有显示定义默认构造函数的const类类型变量(可以参考该博文合成的默认构造函数定义为delete的一种情况)
b)引用类型成员
c)没有默认构造函数的类类型变量
其本质是因为,const内置类型变量和引用类型必须初始化;而对于类类型对象,可以通过默认构造函数进行默认初始化(非const类类型对象只要有默认构造函数就可以默认初始化,而const类类型对象必须有显示定义的默认构造函数才可以执行默认初始化)
5. 类成员初始化的顺序是它们在类中声明的顺序,而不是初始化列表中列出的顺序
考虑下面的类:
class X { int i; int j; public: X(int val) : j(val), i(j) { } };
我们的设想是这样的,用val初始化j,用j的值初始化i,然而这里初始化的次序是先i然后j。
记住:类成员初始化的顺序是它们在类中声明的顺序,而不是初始化列表中列出的顺序!
6. 析构函数
与构造函数一样,析构函数也是一种特殊的函数。构造函数在对象被创建时调用,析构函数则是在对象被销毁时被调用。构造函数与构造函数一样,同样没有返回值,并且析构函数没有任何参数。如下:
~Person(){
}
需要引起注意的是:
a)对于类类型对象foo的析构函数只是在它生命期的最后一刻的回调罢了,管不了foo自己所占的内存,就像自己没法给自己收尸一样。
b)对于堆上的类类型对象:free() 干的事情是释放内存。delete 干的事情是调用析构函数,然后释放内存,注意是delete释放的内存空间,而不是析构函数释放的。对于栈上的类类型对象,退出作用域时会自动调用析构函数,然后释放内存。
总结:对于栈上的类类型对象其实和内置类型变量一样,退出作用域后都是由系统自动释放内存的。实际上无论是栈空间,还是堆空间,内置类型对象和类类型对象销毁时的区别,在于类对象会在销毁前调用析构函数。
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。