C++学习笔记(二)
复习回顾
- 私有继承时,父类成员在子类中是什么访问属性?
都变成私有属性 .在类外就无法访问. 在子类内部可以访问父类原有的保护成员和公有成员. - 什么是重定义? 什么是重载?
2.1 重定义 - 不同作用域(父类和子类) , 函数名相同, 参数可以不同. 通过子类对象调用该重定义的函数时,默认调用的是子类的(就近原则)
2.2 重载 - 同一个作用域, 函数名相同,形参 不同(类型,顺序,个数). - 怎么在子类中调用父类成员函数?
3.1 重定义时, 需要加上父类名和作用域选择符: 父类::函数名();
3.2 没有重定义时, 直接通过函数名调用. - 继承的作用有哪些?(包括单继承和多继承)
为了代码复用
4.1 扩展旧类的功能
4.2 组合多个类的功能.
4.3 代码重构 - 什么是继承? 什么是组合?
继承和组合都是一种类和类之间的关系.
继承指的是基类和派生类的关系. 组合是一种包含关系.
继承关系是必须符合一个逻辑: B类是A类中的一种.
组合关系是必须符合一个逻辑: B类是A类中的一部分.
//组合关系
class A{
public: int m_nNum;
};
class B{
public:
A m_obj; //组合
void fun(){
m_obj.m_nNum = 0;
}
};
//继承关系
class A1{
public:
int m_nNum;
};
class B1 : public A1 {
public:
void fun(){
m_nNum = 0;
}
};
- 继承之后,子类对象的大小是多大?
是所有父类成员的大小加上子类自身成员的大小(如果同名的成员变量,那么这另个变量各自占有独立的内存空
间). - 继承之后, 父类构造和子类构造的调用顺序是什么?析构呢?
构造 : 父类-它类成员对象构造-子类
析构 : 子类-它类成员对象析构-父类 - 如果父类没有无参构造函数,程序能否编译成功?如果不能该如何解决?
不能.通过显示调用: 在子类的构造函数的初始化列表中通过父类类名来调用父类的其它重载版本的构造函数.
class A{
public:
// A(){} 被编译器删除.
A( int n ){}
};
class B : public A{
MyClass m_obj;
public:
B():A(0),/*调用父类的其它重载版本的构造函数*/
m_obj( 0 )/*调用对象的其它版本的构造函数*/
{
}
};
- 如何使用一个数组保存不同类型的类对象?
定义一个基类, 然后将这些不同的类型都继承于这个基类. 使用基类指针数组就能保存这些类的对象(地址)
class Object{
public:
int m_nNum;
virtual void fun(){cout<<"Object::fun\n";}
};
class A : public Object {
public:
void fun(){cout<<"A::fun\n";}
};
class B : public Object{
public:
void fun(){cout<<"B::fun\n";}
};
class C: public Object{
public:
void fun(){cout<<"C::fun\n";}
};
int main()
{
Object* pObj;
A objA;
pObj = &objA; // 父类指针可以直接保存子类对象的地址.
B objB;
C objC;
Object* arr[3]={&objA, &objB, &objC};
arr[0]->fun(); // 调用了哪个? 无论何种情况都是调用父类(就近原则)
}
- 使用一个数组保存不同类型的类对象之后,怎么调用这些对象的成员函数?
- 想要通过父类指针调用子类的成员函数, 就必须确保在子类中有这个名字和参数的成员函数. 如何确保: 在父类中定义这个成员函数, 这样通过继承的特性,就能使子类中自动继承这个成员函数.
- 如果子类中重新定义了父类中的这个同名函数. 那么通过父类指针调用该同名函数时,调用的父类的成员函数
多态性和虚函数
多态就是不同的对象对于相同的指令会做出不同的行为。
C++中实现多态有以下多种方式:
- 函数重载
- 运算符重载
- 模板
- 虚函数
重载,重定义与重写的区别:
重用特征 | 英文 | 作用域 | 域名 | 参数 | 备注 |
---|---|---|---|---|---|
重载 | Overload | 相同 | 相同 | 不同 | |
重定义 | Overwrite | 不同 | 相同 | 相同/不同 | |
重写 | Override | 不同 | 相同 | 相同 | Virtual |
- 重 载:同一种函数的不同实现
- 重定义:子类覆盖基类的同名函数,函数类型可不同
- 重 写:子类覆盖基类的同名虚函数,函数类型相同
联编:是指程序自身彼此关联的过程.
- 静态联编:是指在程序编译链接阶段进行联编,也成为早期联编.
- 这种联编工作在程序运行之前完成,所调用的函数与执行代码之间的关系已经确定
- 动态联编:是指在程序运行过程时进行的联编.
- 要求在程序运行过程中解决程序函数调用与执行代码之间的关系.
- 条件:
- 被调用函数是虚函数
- 必须通过基类指针指向子类对象
- 基类指针调用虚函数
虚函数
- 一个函数在基类中被声明为虚函数,则它在所有派生类中都是虚函数
- 只要通过基类指针与引用调用虚函数才能引发动态绑定
- 虚函数不能声明为静态,也不能是友元函数
- 基类中的虚函数,在派生类中即使没有指定vitural关键字,它也是虚函数.
- 如果某个类中的一个成员函数被说明为虚函数,该成员函数可能在派生类中存在不同的实现版本.
虚析构函数
- 正常情况下一个子类被释放,会主动调用父类的析构函数
- 不使用虚析构函数,使用父类指针指向子类对象,当释放该父类指针时,会调用父类的析构函数,此时子类中若有堆内存的申请,会造成内存泄漏.
- 使用虚析构函数,使用父类指针指向子类对象,当释放该父类指针时,会调用子类的虚析构函数,再调用父类的虚析构函数
纯虚函数和抽象类
- 纯虚函数就是一个只有声明,没有定义的的虚函
- 基类中,基类虚函数没有函数体实现,仅仅当作统一的编程接口来使用,他的实现留给子类去做
- 子类若没有重写虚函数,也是抽象类
- 抽象类不能定义对象,可以定义指针和引用
模板
泛型编程.
函数模板
- 定义的语法: template void fun(T n);
- 定义出来的函数模板并不是真正被执行的代码 , 当函数模板被调用时, 编译器会用这个模板生成一份带有具体数据类型的模板函数,然后再调用模板函数(函数模板并不会被直接调用).
- 调用顺序(匹配顺序)
1.根据函数名找到一个同名的普通函数, 并检查参数是否匹配
2.根据函数名找到一个实例化的模板函数.找到则调用
3.根据函数名找到一个函数模板, 并使用实参的类型来实例化成一个模板函数, 并调用该函数.
模板特化
- 偏特化
- 全特化
类模板
- 类模板不能分为两个文件. 只能放在一个文件中定义.(也就说不能将声明和定义分开存放, 如果分开了, 会报莫名其妙的错)
- 类模板的成员函数可以在类外定义, 在类外定义的时候, 需要在每个函数名前加上模板的说明.