类与对象
C++ 支持面向对象编程,类是 C++ 的核心特性。类用于指定对象的形式,包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员。
类定义,本质上是定义一个数据类型的蓝图。类定义是以关键字 class 开头,后跟类的名称。类的主体是包含在一对花括号中。类定义后必须跟着一个分号或一个声明列表。
完成类定义后可以声明类的对象,就像声明基本类型的变量一样。
Box Box1; // 声明 Box1,类型为 Box
类的对象的公共(public)数据成员可以使用直接成员访问运算符** .** 来访问,而私有的成员和受保护的成员不能使用直接成员访问运算符 (.) 来直接访问。
Box1.height = 5.0;
类的对象的成员函数在类的外部使用范围解析符**:: ** 来定义,而私有的成员和受保护的成员则不能。
double Box::getVolume(void)
{
return length * breadth * height;
}
类访问修饰符
对重要数据进行封装是面向对象的重要内容,防止函数直接访问类的内部成员,类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。
class Base {
public: // 公有成员
protected: // 受保护成员
private: // 私有成员
};
-
公有(public)成员在类的外部是可直接访问的,可以不使用任何成员函数来设置和获取公有变量的值。
-
私有(private)成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。默认情况下,类的所有成员都是私有的。
-
受保护(protected)成员成员变量或函数与私有成员十分相似,但有一点不同,其在派生类(即子类)中是可访问的。
构造函数
类的一种特殊的成员函数,它会在每次创建类的新对象时执行。其名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。
class Line
{
Line(void)
{ }
};
构造函数也可以直接带有参数。这样在创建对象时就会给对象赋初始值。
class Line
{
Line(double len)
{
length = len;
}
};
为了快速的初始化赋值,我们还可以采取初始化列表来初始化参数:
Line( int aa, int bb, int cc): a(aa), b(bb), c(cc), d(3.14f), e("Yes")
{ }
析构函数
在每次删除所创建的对象时执行,名称与类的名称相同,只是在前面加了个波浪号(~)。有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
~Line(void)
{
cout << "Object is being deleted" << endl;
}
拷贝构造函数
实现在创建新对象时使用相同类的已有对象来来初始化新对象。一般有以下三个场景。
- 通过使用同类的已有对象来初始化新对象。
- 复制对象作为函数参数。
- 复制对象从函数返回。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
class Line
{
public:
int *ptr;
Line::Line(int len) // 成员函数定义,包括构造函数
{
ptr = new int; // 为指针分配内存
*ptr = len;
}
Line(const Line &obj)
{
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
};
友元
定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。但是友元不属于类,也就是在友元函数中不能出现this指针。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
class AA
{
friend void func();
friend class BB;
};
class BB{};
void func();
内联函数
内联函数的目的是为了解决程序中函数调用的效率问题,一般都是1-5行的小函数。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。但是需要注意:
- 在内联函数内不允许使用循环语句和开关语句;
- 内联函数的定义必须出现在内联函数第一次调用之前;
inline int Max(int x, int y)
{
return (x > y)? x : y;
}
this 指针
每一个对象都能通过 this 指针来访问自己的地址,可以用来指向调用对象,友元函数没有 this 指针,因为友元不是类的成员。
class Box{
public:
Box * get_address() //得到this的地址
{
return this;
}
};
Box* p = box.get_address(); // 获得box对象的地址
指向类的指针
指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->。
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // Declare box1
Box *ptrBox; // Declare pointer to a class.
ptrBox = &Box1; // 保存第一个对象的地址
ptrBox->Volume() // 现在尝试使用成员访问运算符来访问成员
}
类的静态成员
static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。可以分为两类:静态变量和静态函数。
静态变量的初始化不能放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
class Box
{
public:
static int objectCount;
Box(double l=2.0, double b=2.0, double h=2.0)
{
objectCount++;
}
};
int Box::objectCount = 0; // 初始化类 Box 的静态成员
静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。且即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
class Box
{
public:
static int objectCount;
Box(double l=2.0, double b=2.0, double h=2.0)
{
objectCount++; // 每次创建对象时增加 1
}
static int getCount()
{
return objectCount;
}
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
Box::getCount();
继承
继承允许我们基于已存在类来定义新类,达到了重用代码功能和提高执行效率的效果,已有的类称为基类,新类称为派生类。
class Animal { // 基类
// eat() 函数
// sleep() 函数
};
class Dog : public Animal { //派生类
// bark() 函数
};
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
重载
C++ 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。编译器通过把您所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。选择最合适的过程,称为重载决策。
函数重载
在同一个作用域内,可以声明几个同名函数,但是形式参数(指参数的个数、类型或者顺序)必须不同,也不能仅通过返回类型的不同来重载函数。
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
运算符重载
通过在运算符重载 operator 的重载,可以更加快捷的处理对象之间的运算。我们重载大部分 C++ 内置的运算符,例如加、减、乘、除等。
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
多态
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。这种操作被称为动态链接,或后期绑定。
例如:派生类 Student 继承了 Person 基类,基类声明了虚函数,则派生类必须对基类的虚函数进行重写。当外部在调用虚函数时,会根据调用的对象类型来执行不同虚函数。
纯虚函数
在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
class AA {
// 定义纯虚函数
virtual int bb() = 0;
};
数据抽象
数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。
就 C++ 编程而言,C++ 类为数据抽象提供了可能。它们向外界提供了大量用于操作对象数据的公共方法,也就是说,外界实际上并不清楚类的内部实现。这有以下好处:
- 类的内部受到保护,不会因用户错误导致对象状态受损。
- 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求。
数据封装
数据封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。
C++ 通过创建类来支持封装和数据隐藏(public、protected、private)。通常情况下,我们都会设置类成员状态为私有(private),除非我们真的需要将其暴露,这样才能保证良好的封装性。
接口(抽象类)
C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。这些纯虚函数在相应的派生类中被实现。
设计抽象类(通常称为 ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。
评论区