这篇是FishC的《C++快速入门》中第十四讲、第十五讲和第十七讲的笔记整理,因为OneNote的代码插件记笔记太难过了,就把内容写在CSDN上吧。
我们从给一个类开始,首先类是一个模型。当我们为这个类创建实例的时候,也就是对象本身。这跟我们之前讲解的定义和使用结构的过程很相似,但是这更具有扩展性和前瞻性。
对象的内部可以有变量和函数,而结构通常只由各种变量组成。
因此,我们需要知道的是是如何编写一个简单对象的模型 - 类
类(class)就像是一副蓝图,它决定给一个对象将是什么样的(具备什么样的属性和功能)所以OOP过程的第一步是创建一个类,而每个类跟变量一样都有一个名字,我们就从如何声明一个类说起:
class MyFirstClass
{
};
注意,类名的第一个字母采用大写是一种习惯上的标准。还有在类声明的某位,必须有一个分号,这一点跟C++的结构情况相同。
类由变量和函数组成,对象将使用那些变量来存储信息,调用那些函数来完成操作。所以人们常常会看到一些专业术语:
类里边的变量成为属性,函数成为方法。注意:他们的本质没有发生改变。
给类添加方法如出一辙:
#include
class Car
{
public:
std::string color;
std::string engine;
float gas_tank;
unsigned int Wheel;
void fill_tank(float liter);
//方法的声明:方法是“加油”,参数是“公升”
void running(void);
};
void Car::fill_tank(float liter)
//作用域解析符(::),作用是告诉编译器这个方法存在于何处,或者说属于哪一类
{
gas_tank += liter;
}
int main()
{
return 0;
}
上一讲中讨论了使用面向对象编程技术开发程序最基本步骤:
而构造器是类中的一种特殊的方法。
构造器和通常方法的主要区别:
- 构造器的名字必须和它所在的类的名字一样。
- 系统在创建某个类的实例时会第一时间自动调用这个类的构造器。
- 构造器永远不会返回任何值。
例如:
class Car{
Car(void);
}
注意大小写与类名保持一致。在结束声明之后开始定义构造器本身:
Car::Car(void)//不用写 void Car::Car(void)
{
color = "WHITE";
engine = "V8";
wheel = 4;
gas_tank = FULL_GAS;
}
构造对象数组:之前我们已经说过,数组可以是任何一种数据类型,当然也包括对象。
如:Car mycar[10];
调用方法依旧是:mycar[x].running;
注:x表示给定数组元素的下标。
每个类至少有一个构造器,如果你没有在类里面定义一个构造器,那么编译器就会使用如下语法替你定义一个:
ClassName::ClassName(){}
这是一个没有代码内容的空构造器,除此之外,编译器还会替你创建一个副本构造器(CopyConstructor)
从前面的内容我们了解到,在创建对象时,系统都会自动调用一个特殊的方法,即构造器。
相应地,在销毁一个对象时,系统也应该会调用另一个特殊方法达到效果。没错,这就是析构器。
一般来说,构造器用来完成事先的初始化和准备工作(申请分配内存),析构器用来事后时候所必须的清理工作(清理内存)。
构造器和析构器二者相辅相成,有许多共同之处。首先,析构器有着和构造器/类一样的名字,只不过千变多了一个波浪符"~"前缀。
class Car
{
Car(void);
~Car();
}
其次,析构器也永远不带任何返回值。
另外,析构器是不带任何参数的。所以析构器的声明永远是如下格式:
~ClassName();
FishC在课上给出一个完整栗子,如下所示:
#include
#include
#include
class StoreQuote
{
public:
std::string quote, speaker;
std::ofstream fileOutput;
StoreQuote();
~StoreQuote();
void inputQuote();
void inputSpeaker();
bool write();
};
StoreQuote::StoreQuote()//构造器
{
fileOutput.open("test.txt", std::ios::app);
}
StoreQuote::~StoreQuote()//析构器
{
fileOutput.close();
}
void StoreQuote::inputQuote()
{
std::getline(std::cin, quote);
}
void StoreQuote::inputSpeaker()
{
std::getline(std::cin, speaker);
}
bool StoreQuote::write()
{
if (fileOutput.is_open())
{
fileOutput << quote << "|" << speaker << "\n";
return true;
}
else
{
return false;
}
}
int main()
{
StoreQuote quote;
std::cout << "请输入一句名言:\n";
quote.inputQuote();
std::cout << "请输入作者:\n";
quote.inputSpeaker();
if (quote.write())
{
std::cout << "成功写入文件^_^";
}
else
{
std::cout << "写入文件失败T_T";
return 1;
}
return 0;
}
其中用到了本次所讲的构造器和析构器用来打开和关闭文件,其中C++打开和关闭文件可见之前的笔记《【C++入门笔记】文件操作》
以上都是在没有继承机制下的构造器和析构器,很容易理解。但如果使用了继承机制,构造器和析构器就变得有些复杂了。
比如基类有个构造器,如Animal( ),它将在创造Pig类型的对象时最先被调用,如果Pig类也有一个构造器,它将排在第二个被调用。因此,基类必须在子类之前初始化!!
然后我们继续讨论:如果构造器带着输入参数,事情就变得稍微复杂起来。
class Animal{
public:
Animal(std::string theName);
std::string name;
};
class Pig:public Animal{
public:
Pig(std::string theName);
};
Animal::Animal(std::string theName){
name = theName;
}
Pig::Pig(std::string theName):Animal(theName){
}
注意在子类的构造器中定义的":Animal(theName)"语法含义是:
- 当调用Pig( )构造器时(以theName作为输入参数),Animal( )构造器也会被调用(theName作为输入参数将传递给它)
- 于是,当我们调用Pig pig(“小猪猪”);将把字符串“小猪猪”传递给Pig( )和Animal( ),复制动作将实际发生在Animal( )方法里。
看下面这个栗子:
#include
#include
class Animal
{
public:
std::string mouth;
std::string name;
Animal(std::string theName);
void eat();
void sleep();
void drool();
};
class Pig : public Animal
{
public:
void climb();
Pig(std::string theName);
};
class Turtle : public Animal
{
public:
void swim();
Turtle(std::string theName);
};
Animal::Animal(std::string theName)
{
name = theName;
}
void Animal::eat()
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的我会流口水,我正在流口水。。。" << std::endl;
}
Pig::Pig(std::string theName) : Animal(theName)//Pig的构造器
{
}
void Pig::climb()
{
std::cout << "我是一个只漂亮的小母猪猪,我会上树,我正在爬树,嘘。。。" << std::endl;
}
Turtle::Turtle(std::string theName) : Animal(theName)//Turtle的构造器
{
}
void Turtle::swim()
{
std::cout << "我是一只小甲鱼,当母猪想抓我的时候,我就游到海里。。哈哈。。" << std::endl;
}
int main()
{
Pig pig("小猪猪");
Turtle turtle("小甲鱼");
std::cout << "这只猪的名字是: " << pig.name << std::endl;
std::cout << "每只乌龟都有个伟大的名字: " << turtle.name << std::endl;
pig.eat();
turtle.eat();
pig.climb();
turtle.swim();
return 0;
}
这只猪的名字是: 小猪猪
每只乌龟都有个伟大的名字: 小甲鱼
I'm eatting!
I'm eatting!
我是一个只漂亮的小母猪猪,我会上树,我正在爬树,嘘。。。
我是一只小甲鱼,当母猪想抓我的时候,我就游到海里。。哈哈。。
在销毁某个对象时,基类的析构器也会被自动调用,但这些事情编译器会自动帮你处理。
因为析构器不需要输入参数,所以根本用不着使用:SuperClassMethod(arguments)语法
与构造器的情况相反,基类的析构器将在子类的最后一条语句执行完毕后才被调用。
为了清晰地看出在继承机制中构造器和析构器调用的次序,让我们看下一个栗子。
#include
#include
class BaseClass
{
public:
BaseClass();
~BaseClass();
void doSomething();
};
class SubClass : public BaseClass
{
public:
SubClass();
~SubClass();
};
BaseClass::BaseClass()
{
std::cout << "进入基类构造器\n";
std::cout << "我在基类构造器里做了某些事情\n\n";
};
BaseClass :: ~BaseClass()
{
std::cout << "进入基类析构器\n";
std::cout << "我在基类析构器里也做了某些事情\n\n";
}
void BaseClass::doSomething()
{
std::cout << "我干了某些事情\n\n";
}
SubClass::SubClass()
{
std::cout << "进入子类构造器\n";
std::cout << "我在子类构造器里还做了某些事情\n\n";
}
SubClass :: ~SubClass()
{
std::cout << "进入子类析构器\n";
std::cout << "我在子类析构器里还做了某些事情\n\n";
}
int main()
{
SubClass subclass;
subclass.doSomething();
std::cout << "完事,收工!\n";
return 0;
}
进入基类构造器
我在基类构造器里做了某些事情进入子类构造器
我在子类构造器里还做了某些事情我干了某些事情
完事,收工!
进入子类析构器
我在子类析构器里还做了某些事情进入基类析构器
我在基类析构器里也做了某些事情
由结果可见,首先进入基类的构造器 --> 子类构造器 --> 方法 --> 子类析构器 --> 基类析构器
这两节课主要是讲C++作为一门面向对象的程序语言的“对象”问题。
首先“类”是一个抽象的模型,“类”里边的变量称为属性,函数称为方法。而对“类”实例化后就有了“对象”。
一般来说,构造器用来完成事先的初始化和准备工作(申请分配内存),析构器用来事后时候所必须的清理工作(清理内存)。
而在继承机制中的构造器和析构器,调用次序是:基类的构造器 --> 子类构造器 ; 子类析构器 --> 基类析构器
《C++快速入门--小甲鱼》https://www.bilibili.com/video/av7595819/?p=16