文章

四:C++类

四:C++类

前文

较详细的从分解helloworld、数组、字符串,加上大概基本的语法知识点,为的是编写基本的代码, C++是面向对象的一门编程语言,所以此篇较详细更新的是C++的类。

正文

类的概念

在计算机相关课程中,或多或少都接触过,类不过是一组数据、函数的集合(C没有类的概念,C有结构体),面向对象的语言都有类的概念,有了类的概念,接着封装、继承、多态,面向对象的三大特性能帮助更好的编写代码。

在vs中,如果输入class,默认会有一个类的模板:

1
2
3
4
5
6
7
8
9
class MyClass
{
    public:
        MyClass();
        ~MyClass();
    private:
};
MyClass::MyClass(){}
MyClass::~MyClass(){}

(格式稍有调整),上述便是vs这个IDE自带的类模板,稍微解释一下,MyClass()在类被实例化的时候被启动,~MyClass()在类实例化被销毁的时候启动,这在java等语言中都有,注意的是这个模板将这两个函数的定义放在了外面,也就是说类中的函数也可以先声明后定义。

不看模板,一个类简单来说定义如下:

1
2
3
4
5
class Student { // 类的类型首字母大写是约定
    int code, age;
    string name;
    void exam();
}

这就组织了一个类,当然上述省略了很多,但是在大多数面向对象语言中,类就是组织了一些变量、函数。类创建的 对象被称为类的实例化。

上述代码很简单,但也值得思考,前面我们已经接触过类了,如字符串中string类,但注意到没,我们用string类获取字符串 长度的时候,用的是size()函数(或者称方法),这是string提供给我们的,再或者,引入的cout输出和cin输入,我们并不是直接和cin中的变量进行交互,而是getline(),这意味着在设计类的时候,应该暴露出一个方法去与用户(或者说其他代码)进行交互。

那既然有暴露的公用方法(或者可以称为接口)给用户,也有私有的方法,因此首先要引出三个访问控制关键字。

类的变量需要初始化

1
2
3
4
5
class Student {
    int code, age;
    string name;
    void exam();
}

上述代码在vs中会提示报错,因为没有对codeage进行初始化,与Java等默认初始化不同的是, C++变量需要自己初始化或在构造函数中初始化。

问题1:类中数据的访问控制

从上节中应该知道尽量暴露接口给用户而不是直接让用户操作数据,C++提供了private、protected和public三个关键字 来修饰类的中的变量或函数,如:

1
2
3
4
5
6
7
8
9
class Student {
    private:
        int code, age;
        string name;
        void setAge();
    public:
        void exam();
        void setBirthday();
}

上述中,注意变量应该放在叫private的修饰符下,这意味只有Student抖括号中才能访问它,哪怕是main函数也不行, 而public意味着使用类对象的程序都可以访问。此外private不用显式的声明,默认是private。

类和结构体

C++中仍然保留了结构体这种数据类型,与类不同的是结构体的默认访问限制是public(注意,C结构体没有private这种访问修饰,也没有成员函数等),如:

1
2
3
4
5
struct Student
{
    int code;
    string name;
};

(和class不同的是,结构体用struct定义),当然,这里声明结构体变量时需要struct Student zhangsan;,这只是语法不同。与类不同的是,结构体应该是一堆数据的集合,所以,不要像类那样放很多功能。

类的构造函数和析构函数

如第一节,与类同名的函数为构造函数,结合C++函数章节,构造函数也可以重载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Student
{
	public:
		int code = 0;
		string name = "";
		int score;
		Student() { score = 0; }
		Student(int a) { score = a; }
		~Student() {};
};

int main()
{
	Student zhangsan;
	cout << zhangsan.score << endl;
	Student lisi(90);
	cout << lisi.score << endl;
	return 0;
}

构造函数通常用来做初始化设置,如需要的内存大小等。

相反的,~Student()被称为析构函数,在实例化对象被销毁时启动。这有个问题,什么时候被销毁,一般来说, 不同地方的变量生命周期不一样,如函数内的,在右大抖括号结束时整个作用域就结束了。析构函数也可以被调用, 当然,即使被调用,实例化对象被销毁时还是会调用一次,所以不建议自己调用。

什么时候被销毁-作用域

补充上一节作用域的概念,一般来说,程序都是运行在内存中的,但编译器不会将整个内存都设置为public, 肯定是有自身的机制划分了区域,这个具体在代码中是:

  • 局部作用域:这就是上一节说的在函数调用时创建,结束时销毁,作用在函数内部
  • 全局作用域:在所有函数和代码块之外的变量,这意味着整个程序都可访问并且在整个程序开始时创建,结束时候销毁
  • 块作用域:代码块内
  • 类作用域:类内

所谓的一个程序即C++从main函数开始运行到main函数结束这个过程中用到的如链接到动态链接库的代码、头文件代码等。

封装

类的访问修饰符机制可以让我们隐藏一些具体的实现或数据,这也是封装的概念,将具体的实现细节使用private或相应的访问修饰符进行修饰,仅向用户暴露接口。

  • private意味着类中可以访问(成员函数)
  • public意味着程序都可以访问

不需要知道具体的实现细节的好处有很多,如暴露了一个sort方法给用户排序,但实际sort的实现可以随着程序版本的迭代而不断优化,但对其他代码来说,每次调用sort即可。

类的继承

继承即允许一个类(称为子类或派生类)从另一个类(称为父类或基类)继承属性和行为。通过继承,子类可以重用父类的成员变量和成员函数,同时可以添加新的成员变量和成员函数,或者修改已有的成员函数以满足子类的特定需求。 显然继承可以简洁代码。

基本语法:

1
2
3
4
5
6
7
8
9
// 基类
class Animal {
    // eat() 函数
    // sleep() 函数
};
//派生类
class Dog : public Animal {
    // bark() 函数
};

新的类 : 访问修饰符 已存在类,注意到这个访问修饰符,这是因为继承复用了一部分代码,显然的是可能有些代码是不想被复用的, 所以这里需要对子类对父类的权限加以控制,这里的访问修饰符便是控制父类的权限的。 现在说明封装中修饰符protected的权限:用其修饰类中的变量函数,意思是只有父类或其子类的实例才能访问。

同样继承的访问控制:

  • public,意味着子类可以会保留父类的权限,子类的实例可以访问父类的公有成员
  • private,意味着父类的成员在子类中属性私有,即子类类中(成员函数)可以访问
  • protected,意味着在私有的基础上,子类或其子类子类等访问

注意,这不意味着父类中被private修饰的区域也能被子类使用,父类中的权限还是正常的权限。

同样的,C++也支持多继承,语法新的类 : 访问修饰符 已存在类, 访问修饰符 已存在类

类的多态

首先说明多态和函数重载不是一回事,函数重载是根据不同的参数由编译器选择函数执行,在类中,想想在继承中,子类继承父类,还会存在一些问题,如多个子类中会不会有同名、同参数的方法,那如果这样,调用时应该执行哪个?其次,运算符重载可以算做一种多态,因为如<<也涉及到在不同情况下对相同操作的处理。

对于上述问题,编译器允许其存在,并且给出了特定语法,此外将这种不同类的对象对同一消息作出响应,表现出不同行为的情况称为多态,基本语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Shape {
   public:
      virtual void area()
      {
         cout << "Parent class area" <<endl;
      }
};
class Rectangle: public Shape{
   public:
      void area ()
      { 
         cout << "Rectangle class area" <<endl;
      }
};
class Triangle: public Shape{
   public:
      void area ()
      { 
         cout << "Triangle class area" <<endl;
      }
};
int main( )
{
   Shape *shape;
   Rectangle rec;
   Triangle  tri;
   shape = &rec;
   shape->area();
   shape = &tri;
   shape->area();
   return 0;
}
// output: 
// Rectangle class area
// Triangle class area

注意,这里的virtual是必须的,这是编译器给出的语法,virtual意味着请不要使用(静态链接)父类的area()方法,请在程序运行时,根据对象的类型动态链接area()方法。

当然,上面virtual语法也可以改成virtual int area() = 0; = 0意味告诉编译器这是一个纯虚函数,具体实现在子类中。

抽象类(接口)

有了virtual的语法,意味着可以更好的组织编程,因为设计者往往可以编写好没有具体实现的接口,告诉实现者进行具体实现。

C++规定如果类中至少有一个函数被声明为纯虚函数 = 0,则这个类就是抽象类,抽象类不能被实例化, 而其子类若需要实例化,则必须实现每个纯虚函数。

一些想法

学习一门语言也是学习发明这门语言人的思路、想法,倒也不用将一门语言当成数理化这种学科对待,如iostream没有后缀, 当然也许当时也可以加后缀区分, = 0代表纯虚函数,当然好像也可以定义pura virtual关键字代表虚函数, 但这好像也没那么重要,这更像是一群(一个)聪明的制定者制定了一些语法规则,配合编译器,高效的执行代码。

重要的还是程序(一堆二进制代码)运行的本质是什么,寄存器、计数器等等。

本文由作者按照 CC BY 4.0 进行授权

热门标签