CPP 高级
CPP 的 OOP 初步
类
面向对象的四个重要特性,抽象,封装,继承和多态。而类就是对现实事物的抽象,包括属性和方法。
class 类名
{
public:
公有成员
protected:
保护成员 // protected和private的区别就是派生类成员是否可以访问
private:
私有成员 // 若是直接跟在类名之后定义,则private关键字可以省略,即默认为private
};
class Clock
{
int hour, minute, second;
public:
void set_time(int h, int m, int s);
void show_time();
};
void Clock::set_time(int h, int m, int s)
{
this->hour = h;
thi->minute = m;
this->second = s;
}
void Clock::show_time()
{
printf("%d:%d:%d\n", this->hour, this->minute, this->second);
}
构造函数与析构函数
构造函数
在定义一个对象时,由系统自动调用的初始化函数。构造函数无返回值,且与类同名。
若没有自定义构造函数,编译器会默认生成一个无参的构造函数,其中构造函数可以重载,带默认参数等。
拷贝构造函数
拷贝构造函数时一种特殊的构造函数,其参数是本类对象的引用,当用一个对象去初始化另一个对象,函数参数传递,函数返回类对象等时,系统会调用拷贝构造函数。
同构造函数,若没有自定义构造函数,编译器会默认生成一个的拷贝构造函数。
析构函数
当对象内存被释放时被自动调用,同理,编译器也会默认生成一个析构函数。
class Clock
{
int hour, minute, second;
public:
Clock(); // 默认的构造函数
Clock(int h, int m, int s); // 自定义构造函数
Clock(Clock &c); // 拷贝构造函数
~Clock(); // 析构函数
void set_time(int h, int m, int s);
void show_time();
};
Clock::Clock()
{
cout << "默认构造函数被调用" << endl;
}
Clock::Clock(int h, int m, int s): hour(h), minute(m), second(s)
{
cout << "自定义构造函数被调用" << endl;
}
Clock::Clock(Clock &c)
{
this->hour = c.hour;
this->minute = c.minute;
this->second = c.second;
cout << "拷贝构造函数被调用" << endl;
}
Clock::~Clock()
{
cout << "析构函数被调用" << endl;
}
静态属性和静态函数
静态成员以static
修饰,其含义不再依附于对象存在,而是属于类本身,是类共有的属性和函数。
- 静态属性:必须为
public
,且在类外初始化,用::
指明其所属类。在类外初始化时要指明变量类型。 - 静态函数:可用对象名或类名调用,在函数内部只能使用静态属性。在类外实现时不需要加
static
。
class Clock
{
int hour, minute, second;
public:
static int count;
static int get_count();
Clock(); // 默认的构造函数
Clock(int h, int m, int s); // 自定义构造函数
Clock(Clock &c); // 拷贝构造函数
~Clock(); // 析构函数
void set_time(int h, int m, int s);
void show_time();
};
int Clock::count = 0;
int Clock::get_count()
{
return Clock::count;
}
友元关系
友元是 CPP 提供的一种破坏数据封装性的机制。通过声明 B 是 A 的友元类或友元函数,B 可以访问 A 的private
属性和函数。
所以友元是基于类的,因为只有类才有private
属性。
友元关系是单向的,不可以继承。
友元函数
class Point
{
int x, y;
public:
friend float dist(Point a, Point b);
};
float dist(Point a, Point b)
{
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(x*x, y*y);
}
友元类
class A
{
public:
friend class B;
private:
int x, y;
};
继承和派生
继承
CPP 支持多继承。
class 派生类名: 继承方式1 基类名1, 继承方式2 基类名2
{
// xx
};
- 公有继承(
public
)- 基类的
public
和protected
在派生类中的属性不变,private
不可访问。 - 派生类的成员函数只能访问基类的
public
和protected
。 - 派生类的对象只能访问基类的
public
。
- 基类的
- 私有继承(
protected
)- 基类的
public
和protected
在派生类中均为private
,private
不可访问。 - 派生类的成员函数只能访问基类的
public
和protected
。 - 派生类的对象不可访问任何基类的任何数据。
- 基类的
- 保护继承(
protected
)- 基类的
public
和protected
在派生类中均为protected
,private
不可访问。 - 派生类的成员函数只能访问基类的
public
和protected
。 - 派生类的对象不可访问任何基类的任何数据。
- 基类的
多继承的构造函数与析构函数
- 由于派生类有多个基类,所以在初始化派生类之前需要对基类也进行初始化。
- 多继承的构造顺序是按照继承时的顺序,析构函数就是相反的顺序。
- 若不显式地调用基类的构造函数,系统会自动调用基类缺省的构造函数。
class A
{
int a;
public:
A();
A(int a);
int get_a(){return a;}
};
A::A()
{
cout << "A的默认构造函数被调用" << endl;
}
A::A(int a) : a(a)
{
cout << "A的自定义构造函数被调用" << endl;
}
class B : public A
{
public:
B();
B(int a);
};
B::B()
{
cout << "B的默认构造函数被调用" << endl;
}
B::B(int a): A(a)
{
cout << "B的自定义构造函数被调用" << endl;
}
同名隐藏规则
当派生类与基类有成员变量同名时:
- 未指定类名时,默认指派生类成员。
- 如果非要通过派生类对象访问基类同名成员变量,应指明类名,如
a.Base1::var
二义性问题
在多继承时,出现同名成员时:
- 对于成员变量,可以通过同名隐藏规则解决。
- 对于成员函数,可以通过虚函数解决。
class A
{
public:
void f();
};
class B
{
public:
void f();
void g();
};
class C : public A, public B
{
};
// c.f() // 此时,就会出现二义性,可以通过同名隐藏规则解决
c.A::f()
c.B::f()
还有另一种情况,当派生类从多个基类派生,而这些基类又从同一个基基类派生,则在访问基基类所拥有的成员变量时,就会产生二义性,这是需要用到虚基类解决。
class B
{
public:
int b;
};
class B1 : public B {};
class B2 : public B {};
class C : public B1, public B2 {};
c.b // 出现二义性问题,request for member 'b' is ambiguous
虚基类
虚基类用于解决拥有共同基类的二义性问题。在继承时共同基类时加virtual
即可。
虚基类继承时,只为最远的派生类提供唯一的基类成员,不会产生重复。这里的最远派生类是指定义对象时所指定的类的基基类,此时共同成员由最远派生类来初始化。
class B
{
public:
int nv;
B(){}
B(int n){ nv = n; cout << "Member of B" << endl; }
void fun(){ cout << "fun of B" << endl; }
};
class B1 :virtual public B {};
class B2 :virtual public B {};
class C :public B1, public B2
{
public:
C(int a) :B(a){ cout << "Member of C" << endl; }
};
c.nv // 1
c.fun() // fun of B
多态
多态是同一事物在不同条件下的执行结果不同。在 CPP 中,实现多态有 3 中方法,函数重载,运算符重载和虚函数。
函数重载
函数名相同,函数的参数个数或者参数类型不同,则称之为函数重载。
函数的返回值类型不可以作为判断函数重载的依据。
运算符重载
虚函数
CPP 中的子类也可以自动向上转型,即子类可以当做父类用,但是此时只能使用从父类继承的成员,调用的函数也是父类的函数体,没有实现动态绑定。在 CPP 中要实现动态绑定,则需要用到虚函数+指针/引用
- 虚函数是动态绑定的基础。
- 虚函数只能是非静态成员函数。
- 在基类的函数返回类型前加关键字
virtual
即可。 - 虚函数只能用在定义,在实现时不可以加。
- 虚函数具有继承性,后续的重写函数仍然是虚函数。
- 虚函数必须使用指针或引用调用,否则仍然没有动态绑定。
class A
{
public:
virtual void fun(){cout << "fun of A" << endl;}
};
class B : public A
{
public:
void fun() { cout << "fun of B" << endl;}
};
void allfun(A &a)
{
a.fun();
}
int main(void)
{
B b;
allfun(b); // 如果void allfun(A a),则结果仍为"fun of A"
return 0;
}
纯虚函数与抽象类
- 纯虚函数:
虚函数 = 0;
- 抽象类:至少含有一个纯虚函数的类。
- 抽象类不可以实例化,必须继承,派生类必须重写纯虚函数。