IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++ 语言学习入门--类 -> 正文阅读

[C++知识库]C++ 语言学习入门--类


【PS】本篇文章基本上用于自己学习的备份。如果有任何理解有问题的地方请在评论中指出,谢谢!
【PPS】本篇文章的引文基本均出自 Starting Out with C++ From Control Structures Through Objects 8th Global Edition 如果有兴趣的可以去自己学习研究。

1. 面对对象的语言(object-oriented programming (OOP))

与C语言不同,C++是面对对象的语言。与其相对的,是面向过程的语言?。顾名思义,面对对象的语言是围绕"对象(Obejct)"进行的编程。

a. 为什么要使用面对对象的语言?

Procedural programming has worked well for software developers for many years. However, as programs become larger and more complex, the separation of a program’s data and the code that operates on the data can lead to problems. For example, the data in a procedural program are stored in variables, as well as more complex structures that are created from variables. The procedures that operate on the data must be designed with those variables and data structures in mind. But, what happens if the format of the data is altered? Quite often, a program’s specifications change, resulting in redesigned data structures. When the structure of the data changes, the code that operates on the data must also change to accept the new format. This results in additional work for programmers and a greater opportunity for bugs to appear in the code.

面对过程的语言对于数据是直接通过过程(procedure/method)对数据进行处理。这样带来的结果是,如果数据的格式发生了改变,则整个处理过程的框架都会发生改变,修改这整个过程框架就会产生漏洞。下图是面对过程的语言编程的大致架构。
在这里插入图片描述
面对对象的语言则是通过接口与外部进行链接,因此如果出现原始的数据结构改变时,外部的整体框架并不会发生改变,只需要改变这一个对象即可,因此大大减少了维护的复杂度。在大型的项目和复杂额程序中,OOP的优势即可显现。下图是面对对象的语言的大致架构。
在这里插入图片描述

b. 对象 (Object) 是干什么的 ?

An object is not a stand-alone program, but is used by programs that need its service. For example, Sharon is a programmer who has developed an object for rendering 3D images. She is a math whiz and knows a lot about computer graphics, so her object is coded to perform all the necessary 3D mathematical operations and handle the computer s video hardware. Tom, who is writing a program for an architectural firm, needs his application to display 3D images of buildings. Because he is working under a tight deadline and does not possess a great deal of knowledge about computer graphics, he can use Sharon s object to perform the 3D rendering.

对象不是一个独立的程序,而是被需要它服务的程序所使用。

例如,Sharon是一名程序员,她开发了一种用于渲染3D图像的对象。她是数学天才,对计算机图形学非常了解,所以她的目标是执行所有必要的3D数学运算,并处理计算机的视频硬件。汤姆正在为一家建筑公司编写程序,他需要他的应用程序来显示建筑物的3D图像。因为他的工作时间很紧,而且没有大量的计算机图形学知识,所以他可以使用Sharon的对象来执行3D渲染。

因此对象是服务于程序的。

2. 类(Class)

a. 类是什么?

A class is code that specifies the attributes and member functions that a particular type of object may have. So, a class is not an object, but it is a description of an object.

意思是:类是指定特定类型对象可能具有的属性和成员函数的代码。所以,类是一个描述对象的东西,而非对象本身。

形象一点的说法是,”类“就好比一个个房子,它可以供人居住和使用(供程序调用)。而”对象“就是里面的客厅,厨房,卧室等等房间(有各自的功能)。这些房间都有特定的用处,整个房子也是由这些有用的房间组成的。当然,这个房子里有些地方是可以让人随意进出的(类中的对象是公共的);也有些地方是不能上锁的(类中的对象是可以私有的)。

因此,在类中,包含有”成员变量(member variable)“和”成员函数(member function)“。每个成员有有可能是公共的/外部可访问的 (public) 或者是自己的/不可外部访问的 (private)。

b. 类的定义

在C++中,最简单的定义一个类:

class class_name
{
	declaration;
	// ... more declarations
	// may follow...
};

//例如
class Rectangle
{
	double width;
	double length;
}; 
//有分号结尾 跟struct一样

但是对于类而言,为了数据的保护,会分为两种类型,private和public,这两种类型中,public是可以通过外部直接访问的成员,而private是不可以外部直接访问的,但是类的内部可以访问和调用(使用类内其他成员调用private成员)。

对于类里直接定义的变量来说,默认为private。因此,为了注明每一个成员是private还是public的,我们一般这样定义class:

class ClassName
{
	private:
		// Declarations of private
		// members appear here.
	public:
		// Declarations of public
		// members appear here.
};

//例如
class Rectangle
{
	public:
		void setWidth(double);
		void setLength(double);
		double getWidth() const;
		double getLength() const;
	private:
		double width;
		double length;
};

其中,在函数后面添加const,意思是在这个函数不会更改存储在调用对象中的任何数据。例如这种函数一般只输出等。

如果成员函数需要输入,则只需在括号内添加数据类型即可。例如要两个输入:

void setWidth(double, double)

通常情况下,为了类的整洁和提高代码的可读性,一般会在类中先定义,然后再类外写对应的函数。

void Rectangle::set Width(double w)
{
	width = w;
}

void Rectangle::getWidth() const
{
	cout<<width<<endl;
}

先声明函数,然后再写作用域(是那个类的)最后写出里面的哪个函数。
其中" :: "这个符号叫生存空间解析操作符 (scope resolution operator),表示的是”域“,用于说明这是哪个类下的成员。通俗的说,就是说明了是哪个房子的卧室一样(也可以用于区别不同类的重名函数,这个在一个子类继承多个父类,但是包含同名的函数时可以使用。)

c. 访问器与写值器(Accessors and Mutators)

【PS】由于是国外原著,因此对应得中文并不是很明确。如果知道准确的英文对应的中文请帮我改正,谢谢。

对于一个类而言,习惯上我们将所有变量成为private,然后通过相关的成员函数来读和写内部的变量。

从类中获取值但是不改变值得成员函数称之为Accessor(一般函数是const的)
在成员变量中存储值或以其他方式更改成员变量值的成员函数称为Mutator

为什么我们需要将有变量隐私并且设计看起来毫无作用的访问器和写值器这种多此一举的函数?
在学习编程的过程中,用户是自己,所以看起来像是向自己隐藏数据。但是,如果你在工业中编写软件,你创建的类将被用作大型软件系统的组件; 不是你自己或者熟人使用你的类。所以,通过隐藏类的数据并只允许通过类的成员函数访问它,这就可以更好地确保类将按照自己的预期进行操作。

下图就是将类中最重要的两个参数设置为private,并使用Accessor和Mutators去改写和访问:
在这里插入图片描述

d. 实例化(Instance)

定义了一个类就好比拿到了一张蓝图,但是蓝图终究是一张图纸,现在我们需要依据这个蓝图造出真实的房子,这就是实例化。

例如,现在我们定义了一个类叫做"Rectangle",现在我们想获得一个实例:

Rectangle box;

以类的名字当作一种数据类型,创建了一个名为box的静态Rectangle类。

如果要一个指针来实例化的话,则有两种方法:

1. 
Rectangle box;             //先定义一个实例
Rectangle *ptr = &box;     //再定义一个这个类的指针,指向当前的类

2.
Rectangle *ptr = nullptr;  //先定义一个空指针
ptr = new Rectangle;       //自动分配内存生成一个类的指针
delete ptr;                //不用后可以删除这个指针

当然,更好的是去使用unique_ptr,意味着只有一个指针能指向当前的类,这样对于数据的保护性更好。

#include<memory>    //使用memory库
unique_ptr<Rectangle> rectanglePtr(new Rectangle); 
//建立一个名为rectanglePtr的指针

e. 访问类的成员

实例化了一个类,如何去访问里面的成员?

对于一个静态的实例,通常用点来访问,例如:

box.setWidth(15);

如果定义了一个对应的指针,通常用’->'来进行访问

Rectangle box;
Rectangle *ptr = &box;
ptr->setWidth(15);

f. 类的封装

对于软件工程(Software Engineering)来说,将类通常这么进行封装:

  1. 将类的定义放在自己cpp文件所对应的.h头文件中,这个称之为类规范文件(class specification file)。
  2. 将类的成员函数第一在同一名字的cpp文件下,这个称之为类实现文件(class implementation file)。
  3. 最后在总程序中include其对应的h文件即可使用这个类,类的h文件和cpp文件通常会自行链接。

例如,刚刚的类,我们可以尝试分别创建h文件和cpp文件。在Rectangle.h文件中:

#ifndef RECTANGLE_H
#define RECTANGLE_H

// Rectangle class declaration.
class Rectangle
{
	private:
		double width;
		double length;
		public:
		void setWidth(double);
		void setLength(double);
		double getWidth() const;
		double getLength() const;
		double getArea() const;
};
#endif

可以看到,头文件中只定义了类,但内部的成员函数并未被定义。

然后,在对应的cpp文件中:Rectangle.cpp

#include "Rectangle.h" //先要包含头文件
#include <iostream> 
void Rectangle::setWidth(double w)
{
	if (w >= 0) width = w;
	else
	{
		cout << "Invalid width\n";
		exit(EXIT_FAILURE);
	}
	...
}

在对应的cpp文件中,只定义其成员函数。

这样只需要在主程序中调用Rectangle.h, 即可实现全部类的功能。

为什么要这么做?

将类分为规范文件和实现文件提供了很大的灵活性。第一点是因为,如果你将这个代码与其他程序员要共享,不必和这个程序员共享所有的源代码。我可以给他类实现的规范文件和已编译的对象文件即可。然后只需将必要的#include指令插入到他或她的程序中,编译它,并将它与类的对象文件链接起来。这样的话对于代码的隐蔽性较好,不会被其他程序员知道你的源码。

g. 类中的内联函数成员 (Inline function)

在类中,一些比较简单的成员函数,可以写成内联函数,即直接将函数卸载类定义中。

例如:

double getLength() const
{return length;}
//将此定义直接写在类的定义中

但是内联函数必须要相对简单,不能出现循环、条件、选择等复杂的结构。当然,编译系统有可能也会自动将很简单的函数作为内联函数处理,对于复杂的函数忽略内联处理。

内联函数可以干什么?
一言蔽之,内联函数在一定程度上会占用更多的空间,但是能节约CPU的工作占用时间,提高效率。虽然可能有时根本无法分辨速度快慢,但是它可以在一定程度上节约CPU时间。

h. 构造函数(Constructors)

A constructor is a member function that is automatically called when a class object is created

构造函数是在创建类对象时自动调用的成员函数。还是通俗的理解,像是房子(类)刚建好就送过来的家具一样,会自动的去装修这个房子。一旦这个类被实例化,或者在内存中创造了空间,这个程序即会被执行,像是”初始化一样“。它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用)。与之对应的是解构函数(Destructors),这个在下一章提到。

例如,为一个类定义一个简单构造函数:

class Demo
{
	public:
		Demo();  // 构造函数,和类的名字一模一样
		Demo(string, string); //重载构造函数,可以传递两个参数
};

Demo::Demo()
{
	cout << "Welcome to the constructor!\n";
}

因此,当实例化一个Demo类之后,会自动输出 “Welcome to the constructor!”。

Demo box;          //构造函数会执行
Demo box("A","B"); //构造函数会执行,且使用两个传入的两个string类

但是注意的是

Rectangle ptr = nullptr; //这个不会执行构造函数,因为没有实例被创造出来,只是定义了一个空指针。
ptr = new Recangle;      //这个语句才会执行构造函数,因为Rectangle类被实例化了。

通过构造函数,我们能完成很多东西,例如对于类中private的变量进行初始化值等等。

i. 解构函数(Destructors)

A destructor is a member function that is automatically called when an object is destroyed.

和构造函数相反,解构函数是在这个类被撤销时自动执行的。它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),只不过前面多了一个~号

对于解构函数的定义:

class Demo
{
	public:
		Demo(); // Constructor
		~Demo(); // 解构函数定义
};
//构造函数
Demo::Demo()
{
	cout << "Welcome to the constructor!\n";
}
//解构函数
Demo::~Demo()
{
	cout << "The destructor is now running.\n";
}

解构函数可以在类用不到后及时释放内存。

ptr = new Recangle; //定义了一个指针指向一个Rectangle类
delete ptr;         //销毁一个指针即销毁了当前实例化的类,会执行解构函数

3. 继承(Inheritance)

a. 继承

Inheritance allows a new class to be based on an existing class. The new class inherits all the member variables and functions (except the constructors and destructor) of the class it is based on.

继承就是可以从原先的类中继承其原有的成员函数和变量(除了原先类的构造函数和解构函数->指父类的构造函数不会成为子类的构造函数)。通常称被继承的类叫父类,继承的类叫子类。

为什么需要继承?
继承就像英语中’is a…'一样;就像昆虫分类一样。例如,蛾子是(is a)昆虫,蚱蜢也是(is a)昆虫。那么对于他们而言,有一样的地方(昆虫的基本特征(general characteristic)),也有其自身独特的地方(specific characteristic)。从父类继承子类,可以更加简便的管理代码,不需要在每一个类中定义相同的成员变量和函数。

简单的继承示例如下:

class FinalExam : public GradedActivity
{
    statements...
    ...
}
//FinalExam 是子类,从父类GradedActivity类继承来的,继承的方式是public

在这里插入图片描述

但是值得注意的是,对于继承也有很多方式,分别是public,private,protected。

一图流解释:
在这里插入图片描述
父类的private成员在子类中均不能访问。

这里有一个新的类型:protected。这个类型是子类的函数可以访问父类的成员函数和变量,但是外部依然不能访问的成员。

对于创建的子类的构造函数和传递参数到继承的父类构造函数,使用下面的语法:
在这里插入图片描述

ClassName::ClassName(ParameterList) : BaseClassName(ArgumentList) //定义子类的构造函数
例如:Cube::Cube(double w, double len, double h) : Rectangle(w, len)

当然,子类中也同时可以使用重载产生很多构造函数。

在实例化这个子类时:

Cube Box(double w, double len, double h);
//只需要Cube自身构建函数中的三个变量输入即可,在Cube的构建函数会将变量输入父类构建函数

b. 多继承(Multiple inheritance)

一个子类可以不仅只有一个父类,也可以有很多个父类。

class Cube : public Square, public RectSolid;
//定义一个子类Cube,它继承了Square类和RectSolid类

同样的,定义这个子类的构造函数时,也可以传递参数至父类:

Cube::Cube(int side) : Square(side), RectSolid (side, side, side); 
//定义了Cube的构建函数,将对应的变量传入父类的构造函数中

当实例化这个子类时:

Cube box(side)
// 和单继承的实例化方法一致

继承的父类成员函数的函数名重名怎么办?

  1. 子类中再重定义一个函数
  2. 使用"::"来声明是哪个父类的成员函数。
	ClassName.FunctionName
	子类对象.函数名         //默认调用的是子类自己的同名函数
	ClassName.BaseClassName::FunctionName
	子类对象.父类名::函数名 //调用父类的同名函数

c.多重继承(Hierarchies)

多重继承和多继承不是一个概念。多继承是横向的,即一个子类继承多个父类。但是多重继承是纵向的,即一个子类可能继承一个父类,但是这个父类又是从另一个父类继承下来的。因此,会产生阶层(hierarchies)。

4. 重载、覆写与重定义(Overloaded, Overridden, Redefined)

a. 重载(Overloaded)

Two or more functions may have the same name, as long as their
parameter lists are different.

重载是函数名相同,参数列表不同的函数。一般只在类内出现。
使用重载可以使一种函数实现不同参数输入但能实现相同操作。通俗一点来说,一个房子(类)有两个卫生间,它们都可以上厕所,但是一个可以带纸进去,一个里面自带卫生纸。

使用的方法例如:

class Demo
{
	public:
		Demo();               //构造函数,和类的名字一模一样
		Demo(string, string); //重载构造函数,可以传递两个参数
};
//重载的时候函数名是一模一样的,但是可以有不同输入变量。

调用这个成员函数时,会自动根据变量列表识别运行这个成员函数。

b. 重写(Overridden)

重写是出现在继承中的。如果子类的函数名和父类的函数名相同、变量也相同,且父类中对应的函数成员必须是虚(virtual)函数。

重写会将修改父类的函数,如果子类中重写了父类的函数,则父类的同名成员函数会被覆盖,不可再被调用。

例如:

class Person
{
    public:
        virtual void Eat()
        {
             cout << "Person Eat food" << endl;
        }
        virtual void sleep()
        {
            cout << "Person sleep " << endl;
        }
        void study()
        {
            cout << "We need study" << endl;
        }
};
class Bob : public Person
{
    public:
         void Eat()
        {
             cout << "Lily Eat dumpling" << endl;
         }
         void sleep()
         {
             cout << "Lily sleeps and dreams " << endl;
         }
};
//这个子类的定义就会将父类的两个成员函数覆盖,从而父类的两个成员函数便不可再被调用

注意:
如果子类没有重写此父类的虚成员函数,则默认使用父类的函数调用(没有被重写virtual的函数还是可以使用)。重写是动态链接(dynamic-binding)。即在编译后,依然可以修改父类的函数,不是在编译时确定的。

c. 重定义(Redefine)

重定义也是出现在继承中的。如果子类的函数名和父类中的成员函数名相同(变量表可以不同),则这时候父类的函数就会被重定义。这时候父类的同名成员函数会被隐藏,但是不会被覆盖。

例如:

class Base
{
    public:
        void fun()
        {
            cout << "Base::fun()" << endl;
        }
};
class D :public Base
{
    public:
        void fun(int)
        {
            cout << "D fun(int)" << endl;
        }
};
//这时候如果实例化一个子类,然后调用fun函数,会运行子类的fun函数,父类的不会被执行

当然,如果还是想要调用父类的同名函数,依然可以通过“::”作用域进行约束。

 d.Base::fun(); //调用父类的fun函数

注意:
重定义是静态链接(static-binding)。也就是说,重定义在编译的时候就已经被确定了,和重写/覆盖不同。

5. 多态性(Polymorphism)

6. 虚函数和纯虚函数(Virtual function/Pure virtual function)

7. 抽象基类(Abstract base class)

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-02-19 00:58:09  更:2022-02-19 01:00:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/24 12:47:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码