# 1.类成员
- 类定义本身不定义存储空间,只有定义了类的对象后,系统才会为该变量分配存储空间
- 类定义对象所占据的存储空间只用于存放数据成员,而成员函数并不在每个对象中存有副本
# 2.访问控制
public
成员,可以被类外访问private
成员,只能被该类的成员函数访问,私有部分放在类的最开始部分private
关键字可省略protected
成员,只能被该类的成员函数和派生类的成员函数访问
# 3.类与结构体
C++
中结构体除了具有C
中定义的功能外,还具有类似于类的功能,可以在其中定义函数- 结构体中成员的默认访问权限是
public
,而类中是private
# 4.类的初始化
定义类时不分配内存单元,所以类的定义中不能给数据成员赋值,以下代码将提示语法错误
class C { int a = 1; char b = 'c'; }
变量需要初始化,因此在定义类对象时应该初始化类成员变量,构造函数正是做初始化的成员函数
变量清除工作由类的析构函数完成,构造函数和析构函数由系统自动调用,用户程序不能直接调用
# 5.构造函数与析构函数
public
,与类名相同,没有返回值,不能指定函数类型如果类中没有给出构造函数,
C++
编译器会自动给出一个默认的构造函数析构函数在对象销毁前自动调用,进行一些清理工作,如释放分配给对象的内存空间等
析构函数与类同名,函数名前类
~
,public
,无返回类型和返回值,不带参数,不能重载,一个类中只有一个
# 6.复制构造函数
同个类的对象在内存中有完全相同的结构,故作为整体进行完全复制是可行的
类对象复制,只需要复制数据成员而成员函数是共用的
每个类都必须有个复制构造函数,如果未定义,系统会自动生成一个默认的构造函数
复制构造函数的定义格式:
类名::类名(const 类名&对象名) { }
复制构造函数使用场景:
- 对象做函数形参,调用函数时需将实参对象完整的传递给形参,需要按实参复制一个形参
#include<cstdio> class A{ public: A(){} A(const A&a) { printf("复制构造函数\n"); } }; void f(A n) {} int main() { A a; f(a); // 输出复制构造函数 }
- 函数的返回值是类的对象时,函数调用完毕需要将类对象返回时,会调用类的复制构造函数
- 对象做函数形参,调用函数时需将实参对象完整的传递给形参,需要按实参复制一个形参
# 7.友元friend
- 类中定义的
private
成员,通过对象名是访问不了,如果需在其他函数或类中使用,可以将函数和类声明为当前类的友元 - 如下通过对象访问类中的私有成员,语法错误
class A {
public:
A():m_x(0){}
A(int v):m_x(v){}
~A(){}
private:
int m_x;
}
void printA(A &p) { printf("%d", p.m_x); } // error
int main() {
A a;
printA(a);
return 0;
}
- 声明为友元函数后可访问,同理可以声明友元类
class A {
public:
A():m_x(0){}
A(int v):m_x(v){}
~A(){}
friend void printA(A &p);
private:
int m_x;
}
void printA(A &p) { printf("%d", p.m_x); } // correct
int main() {
A a;
printA(a);
return 0;
}
友元关系不能传递的
友元关系是单向的
# 8.类的成员共享与保护
- 每个类的对象有其自己的数据成员,有时候希望多个类的对象共享同一个类的数据成员,这时可将数据成员和成员函数定义了
static
类型 - 静态数据成员只能被创建及初始化一次,且在对象定义之前,类外进行
- 静态数据成员被所有类共享,不属于对象而属于类
- 静态成员函数也是属于类的,静态成员函数没有this指针,只能访问静态成员,不能访问类的非静态成员
- 常成员函数,不修改对象数据成员的函数应该被修饰为常成员函数
函数返回值类型 函数名(参数)const
- 常对象,定义对象时使用过了const修饰,如
类名 const 对象名
或const 类名 对象名
- 常对象只能访问类的常成员函数
- 常数据成员,使用const修饰的数据成员,如果在一个类中说明了常数据成员,那么构造函数只能通过初始化列表对该数据成员进行初始化
# 9.派生类的构造函数与析构函数
- 派生类不能继承基类的的构造函数和析构函数,当基类含有带参数的构造函数时,派生类必须定义构造函数
Circle(int x, int y, int r):Point(x,y) {};
- 基类与派生类的析构函数是各自独立的
- 派生类继承类基类的成员,所以在构造派生类的对象时,派生类必须调用其基类的构造函数,若没有显式调用,将调用基类的默认构造函数
- 派生类对象创建和销毁时,构造函数和析构函数的调用顺序:
- 构造函数调用顺序
- 基类的构造函数
- 对象成员的构造函数
- 派生类的构造函数
- 析构函数的调用顺序
- 派生类自身的析构函数
- 对象成员的析构函数
- 基类的析构函数
- 构造函数调用顺序
# 10.多重继承
- 多重继承有模糊性
// 说明是设置从Circle1中继承过来的x为10 cc.Circle1::setx(10)
- 虚基类
- 虚基类不能被实例化为对象,其定义如下:
class Derived:virtual public Base1, virtual public Base2 {
}
虚基类的作用就是为其他类提供一个合适的基类,以便派生类可以从它那里继承和实现所需的接口。如上,若Base1,Base2中有共同的基类Base时,为防止二义性,可使用虚基类
派生类到基类对象的转换规则:
- 1)可以用派生类对象为基类对象赋值
- 2)可以用派声类初始化基类的引用
- 3)可以把指向派生类对象的指针赋值给基类对象的指针
- 4)可以把派生类对象的地址赋值给基类对象的指针
若派生类重写了基类的非虚函数时,使用基类的指针指向派生类对象的地址,调用重写的函数将会调用基类定义的函数而非派生类中重写的函数
通过指向派生类的基类指针,指针指向的是派生类中从基类继承来的部分,不能够访问派生类中新声明和定义的方法
zoo::Rectangle *r;
zoo::Cuboid cub3(1, 2, 3);
r = &cub3;
printf("Cub3's area by r: %f\n", r->area()); // 2
printf("Cub3's area by cub3: %f\n", cub3.area()); // 3*(2+6+3)
- 若定义基类中有可能会被派生类重写的函数为
virtual
函数,则通过指向派生类对象的基类指针来调用此函数时,将调用派生类中重写后的函数
class Rectangle {
virtual double area();
...
};
zoo::Rectangle *r;
zoo::Cuboid cub3(1, 2, 3);
r = &cub3;
printf("Cub3's area by r: %f\n", r->area()); // 3*(2+6+3)
printf("Cub3's area by cub3: %f\n", cub3.area()); // 3*(2+6+3)
# 11.虚函数
- 类中被
virtual
关键字修饰的成员函数就是虚函数 - 当把一个函数声明为虚函数时,编译器就在类架构中加上一个指针,该指针被称为虚指针,它指向的是一个虚函数表,该表包含了所有虚函数的地址,也包含其基类
- 虚函数的作用在于实现运行时多态,动态联编
- 成员函数中调用虚函数时,应该将其看成是this指针的显式调用
- 构造函数和析构函数调用虚函数时,C++编译器采用静态联编的方式决定所调用的具体函数,调用的虚函数只能是基类或自己所属类中的虚函数,构造函数本身不能是虚函数
- 虚析构函数:基类指针指向new出来的派生类存储空间时,若基类的析构函数不声明为虚函数,则delete 基类指针时将只会调用基类的析构函数而不会调用派生类的析构函数
#include<cstdio> class Base { public: virtual ~Base(){printf("~Base";)}; } class Derived : public Base { ~Base(){printf("~Derived";)}; } int main(int argc, char ** argv) { Base *p = new Derived(); delete p; return 0; // ~Derived // ~Base }
# 12.纯虚函数与抽象类
- 纯虚函数是在声明虚函数时被初始化为
0
的函数 - 纯虚函数无函数体,只起到为派生类提供一个统一接口的作用
- 在类的成员函数中可以调用纯虚函数
- 具有纯虚函数的类无法用于创建对象,因为纯虚函数没有函数体,所以这种含有纯虚函数的类称为抽象类
- 抽象类不能用作参数类型、函数的返回类型或显式转换的类型,可以声明抽象类的指针和引用,通过它们,可以指向并访问派生类对象,从而访问派生类的成员