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释放的内存空间,而不是析构函数释放的。对于栈上的类类型对象,退出作用域时会自动调用析构函数,然后释放内存。

总结:对于栈上的类类型对象其实和内置类型变量一样,退出作用域后都是由系统自动释放内存的。实际上无论是栈空间,还是堆空间,内置类型对象和类类型对象销毁时的区别,在于类对象会在销毁前调用析构函数。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。