C/C++ 学习笔记:类相关

构造和析构次数

A* p =new A[5];//  构造五次

delete[ ] p;//  析构五次

delete p ; //  若不使用 [ ] ,则析构一次。


假设 A 是一个类,像这样 A a(); 并不是表示调用构造函数创建对象,只是定义了一个函数而已。


绝不重新定义继承而来的缺省参数


若父类没有无参的构造函数,子类需要在自己的构造函数中显式调用父类的构造函数。


构造函数中可以调用虚函数

class Base {
public: 
    Base() { Function();}
    virtual void Function() { cout << "Base::Function" << endl;}
}; 
class A :public Base {
public: 
    A() { Function();} 
    virtual void Function() { cout << "A::Function" << endl;}
};

另外:这样定义一个 A 的对象, A a;  会输出

Base::Function

A::Function

 而不是

A::Function

A::Function

要知道在 Base 的构造函数中调用 Fuction 的时候,调用的是 Base 的 Function。因为此时 A 还没开始构造。



静态成员变量可被该类的所有方法访问。


子类型必须是子类继承了父类的所有可继承特性,也即公有继承,才能说是子类型,否则就只是单纯的子类



重载、重写、隐藏
1. 重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。(它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
2. 重写(覆盖):是指子类重新定义父类虚函数的方法。其函数名、参数列表、返回值类型,所有都必须同基类中被重写的函数一致,只有函数体不同(花括号内)。(和多态真正相关
3. 隐藏:隐藏是指派生类的函数屏蔽了与其同名的基类函数。注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。(和多态无关
重载和重写的区别

1). 范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类中。

2). 参数区别:重写与被重写的函数参数列表、返回类型一定相同;重载和被重载的函数参数列表一定不同,不关心函数返回类型。

class A {
    int function(int i);
    int function(int i) const; // 第一个函数的重载
    int function(const int i);// 编译错误,这里不是重载第一个函数
};

分析

首先 int function(inti)const; 这个函数的形参表和第一个函数是不一样的,因为在类中隐含this 形参的存在这里加上 const 修饰的是 this 指针,则 this 指针的类型就变为指向const 对象的指针。因此,使用指针传参时,指向const  对象的指针和指向非const  对象的指针做形参的函数是不同的。

然后 int function(constint i); 这个函数无法重载第一个函数,因为这个函数和第一个函数对于重载来说是等价的。对于非引用或指针传参,形参是否const  是等价的,但对于引用或者指针形参来说,有无const  是不同的。

3).virtual的区别:重写的基类必须要有virtual 修饰,重载函数和被重载函数可以被virtual 修饰,也可没有。

隐藏和重写,重载的区别

1). 与重载范围不同:隐藏函数和被隐藏函数在不同类中。

2). 参数的区别:隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定同;当参数不同时,无论基类中的函数是否被virtual 修饰,基类函数都是被隐藏,而不是被重写。

总的来说:隐藏和重写,要在不同的类中,如果不满足重写,那就是隐藏

 
友元函数最后面不能加 const ,在函数后面加 const  只适用于成员函数
 
虚函数
虚函数的底层是通过虚函数表实现的。
虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数(static

1. 直接继承

1)每一个具有虚函数的类都有 1 个虚函数表 VTABLE(如果子类有自己的虚函数,那么子类的虚表里面存放继承来的虚函数地址和自己虚函数的地址),里面按在类中声明的虚函数的顺序存放着虚函数的地址,这个虚函数表 VTABLE 是这个类的所有对象所共有的;

2)在每个具有虚函数的类的对象里面都有一个 VPTR 虚函数指针,这个指针指向它自己类的 VTABLE  的首地址,每个类的对象都有这么一种指针。

2. 虚继承

对于虚继承,若派生类有自己的虚函数,则它本身需要有一个虚指针,指向自己的虚表(虚继承的时候,子类自己的虚函数单独有个虚表,然后各个父类的的虚函数分别对应一个虚表)。另外,派生类虚继承父类时,首先要通过加入一个虚指针来指向父类,因此有可能会有两个虚指针。

类对象的大小 = 各非静态数据成员(包括父类的非静态数据成员)的总和+ vfptr指针(多继承下可能不止一个)+ vbptr指针(多继承下可能不止一个)+ 编译器额外增加的字节(对齐)。


C++开发的时候,用来做基类的类的析构函数一般都是虚函数。可是,为什么要这样做呢?
虚析构函数的作用:当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。

 

多态需要通过父类的指针或者引用来实现

如果父类里的虚函数是 private 的,子类依然可以实现多态。虽然父类这个虚函数是 private,但,父类指针或引用可以通过自己的public 方法调用这个私有的虚函数。当然,子类重写的这个虚函数并不是继承来自这个父类的,但是可以实现多态。

 
多继承问题

class A {
public:
    int x;
    A(int X) : x(X) {}
    virtual void print() {
        cout << "A" <<endl;
    }
};
class B {
public:
    int x;
    B(int X) : x(X) {}
    virtual void print() {
        cout << "B" <<endl;
    }
};
class C : public A, public B {
public:
    int x;
    C() : A(1), B(2), x(3) {}
};
 
int main() {
    C c;
    cout << c.A::x << endl;// 输出 1
    cout << c.B::x << endl;// 输出 2
    cout << c.x <<endl;// 输出 3
 
    c.print();// 这里会编译错误,歧义。因为继承了 A 和 B 的 print,无法确定调用哪个。此时,若在 C 中定义了一个 print,就可以这样使用
 
    // 虽然 C 从 A 和 B 继承了 print 函数,但是由于 A 和 B 中都有 print,所以 C 的对象访问的时候要加上 A 或者 B 的域名
    c.A::print();// 输出 A
    c.B::print();// 输出 B
    A* pA = &c;
    B* pB = &c;
    pA->print();// 输出 A
    pB->print();// 输出 B
}


在重载 “+” 运算符中参数要加const,否则,连续相加会编译出错。同理 “- * / ” 也是。
class A {
    int val;
public:
    A(int v):val(v) {}
    friend const A operator+(const A& L, const A& R) {
        A temp(L.val + R.val);
        return temp;
    }
};
A a(1), b(2), c(3);
A d = a+b+c;
因为  operator+(返回值类型是 A,所以 a  b 求值后返回的是一个临时变量,接着这个临时变量再与 c 相加调用 operator+(),临时变量不能修改,所以需要在形参里用 const 引用的形式捕获。
返回值最好加上 const,否则, (b )= c 这样变态的代码也不会编译出错。同理  “- * / ” 也是。

不能被重载的操作符有

.”   “::”   “?:”   “->”   “sizeof”   “#”(预处理符号)(小窍门:带点的操作符肯定不可以重载)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值