老码识途 创造面向对象语言(使用C模拟C++)

  1. 对象相当于结构体变量,其内存布局和去掉成员函数后的等价结构体完全相同。成员函数的数目不会影响对象的大小。(在内存耗费上优于C语言结构体模拟的对象(每个成员函数必须在结构体上对应一个函数指针变量存储其入口地址))
  2. 调用成员函数时都会悄悄传递对象首地址(ECX),并在成员函数中自动获取该首地址,面向对象语言保证了自己操作自己的数据,与结构体有本质区别
  3. C++中的类是所有对象共享一套函数,通过悄悄传递不同对象的首地址,造成了对象有自己独立函数的假象。

构造函数和析构函数
在C语言中,我们需要malloc一个空间,然后使用初始化函数对结构体进行初始化,C++中的new就包含了这2步
C中,构造函数和析构函数在malloc后需要使用初始化函数,C++中通过特殊的标志将这两个函数标定出来
C++中

  1. 构造啦函数做初始化工作,并非分配新对象内存,析构函数做对象回收工作,并非释放对象内存,内存由new操作符分配,释放内存由delete释放。
  2. 构造函数和析构函数都要悄悄传递对象首地址。
  3. 通过new+构造函数,delete+析构函数,将内存分配和初始化,回收和释放内存合二为一。
    C模拟类:

    
    #include<stdio.h>
    
    typedef void (*pShow)();       
    struct cs
    {
    int a;
    pShow show;//每个结构体对象都会有个函数指针
    };
    void Show()
    {
    printf("show");
    }
    void init(struct cs *c)
    {
    c->show=Show;
    }
    int main()
    {
    struct cs c;
    init(&c);
    c.show();
    }
    

    封装
    关键意义:外部调用代码不清楚内部数据的细节,将来这些细节变换时,就影响不到它
    何时调用构造和析构函数 (定义处调用构造函数,花括号前调用析构函数以及1.static 全局在程序结束时 2.局部在block结束 3.delete时 4.异常
    通过整体资源管理,将对象所依赖的资源在构造函数中分配在析构函数中释放。在此基础上,定义局部变量可保证资源的自动分配和释放
    指定的花括号中定义局部变量,能精确地控制构造和析构函数调用的时机
    整体资源分配+局部变量自动调用构造和析构保证了资源的自动分配和释放 是C++这种支持基于栈分配对象语言(C#可以,java不行)的独特技巧

比如:对临界资源的使用需要加锁和解锁,两者必须成对出现,手动添加时会难保证安全,这时我们就可以使用自动打开和释放的技巧:
eg :第一锁类LOCK,将加锁(EnterCriticalSection)放入构造函数,解锁(leaveCriticalSection)放入析构函数,在临界区加入{}并调用LOCK类
{
    LOCK alock(p);//此处自动调用构造函数,而执行EnterCriticalSection
    ..................//此处自动调用析构函数执行LeaveCriticalSectio

注意:1.用指针传递实现了整体资源管理策略的对象,不能直接用对象作为参数。
2.将拷贝构造和=操作符设为私有,保证第一条
3.可以用局部对象实现自动资源管理,但在不同的编译器中,当异常发生时会产生不确定的后果,无法保证析构函数一定被调用去释放资源。
4.在无把握时,C++下,用对象的指针作为参数和局部变量比较简单,副作用小

#include<iostream>
using namespace std;
class A{
public:
    int *a;
    A(){a=new int ;cout<<"constructor"<<endl;}
    ~A(){delete a;cout<<"deconstructor"<<endl;}
    A(A &a){this->a = a.a;};
};
void test(A a)//参数为对象
{
}
int main()
{
    A a;
    test(a);//拷贝构造函数会调用一次析构函数,而拷贝构造函数又是使用引用,会导致2次释放指针,从而报错
}
参考4#include<iostream>
using namespace std;
class A{
public:
    int *a;
    A(){a=new int ;cout<<"constructor"<<endl;}
    ~A(){delete a;cout<<"deconstructor"<<endl;}
//private:
    A(A &a){this->a = a.a;};
};
void test(A *a)
{
}
int main()
{
    A a;
    test(&a);//传递指针,无需调用拷贝构造函数,不会产生A的对象,就不会调用析构函数
}

关于虚函数:
虚函数第一次声明时的顺序(覆盖版的顺序不影响,子类的顺序和父类一样,只不过覆盖了地址)决定了虚表中存储的顺序
生成虚表后,编译器在编译一个有虚函数的类的构造函数时,就可在函数一开始悄悄加入代码,将虚表地址赋值给对象前4字节(注意this指针在对象的-4字节)
可以让成员函数操作一般化,用基类的指针指向不同的派生类的对象时,
基类指针调用其虚成员函数,则会调用其真正指向对象的成员函数, 而不是基类中定义的成员函数(只要派生类改写了该成员函数)。 若不是虚函数,则不管基类指针指向的哪个派生类对象,调用时都会调用基类中定义的那个函数。

在C++中,调用虚函数要用对象的指针才有虚函数效果因为指针无法确定具体的函数地址,而对象的内部地址都已经分配好(类中只是存放了虚表的地址);

在有虚函数的情况下,父子类对象的赋值是不能赋值对象前4字节的,因为虚表地址本质上代表了一个类的行为。
在虚表地址向前偏移4字节的地方存放了类型描述块的地址,判断类型关系的合理逻辑是通过虚表地址获得类B的运行期描述符后,通过它找到父类A的运行期描述,进行比较。
在非虚函数下,父子类因函数名和原型相同产生的覆盖称为静态覆盖。编译器根据以何种类型对象调用该函数来确定调用哪个函数。
静态函数能否声明为虚函数?—————————–C++中不能(虚函数属于对象 静态函数属于类, 虚函数运行时确定的,静态函数是编译时确定的)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值