构造函数主要有如下几种类别:
默认构造函数是指不需要指定参数时的构造函数,其在部分语言中有特殊功能和特殊用法,因此单独成一类。
特殊功能有:
std::list
等时,要求其所存储的对象必须提供一个默认构造函数。ClassName obj();
,而是 ClassName obj;
,前者是声明了一个返回值类型为ClassName的函数。// 调用默认构造函数
ClassName obj;
// 定义一个返回值为ClassName的函数,函数名为obj
ClassName obj();
在C++中,有以下两种方式可以定义默认构造函数:
class Cat {
public:
void printInfo(void);
private:
// 品种
std::string breed;
float weight;
float age;
};
void Cat::printInfo(void)
{
using namespace std;
cout << "breed: " << breed << endl;
cout << "weight: " << weight << endl;
cout << "age: " << age << endl;
}
int main()
{
// 无参构造,默认生成的默认构造函数并不会初始化类中变量
Cat cat1;
// 行为未定义
cat1.printInfo();
return 0;
}
含参构造函数即在创建类时需要指定若干参数。最为常用的方法。
拷贝构造函数用于创建一个对象作为另一个对象的副本。
C++的拷贝构造函数形式为:ClassName(const ClassName &obj)
。
需要注意的是在C++中,拷贝构造函数会被默认生成,其实现方式为浅拷贝。浅拷贝的规则如下:
int
、 double
),会进行直接的值复制。class Cat {
public:
void printInfo(void);
// 默认构造函数
Cat() : breed("default"), weight(0), age(0) {
std::cout << "Called the default constructor." << std::endl;
};
// 含参构造函数
Cat(std::string breed, float weight, float age) : breed(breed), weight(weight), age(age) {
std::cout << "Called a constructor with parameters." << std::endl;
};
// 拷贝构造函数
Cat(const Cat &cat) : breed(cat.breed), weight(cat.weight), age(cat.age) {
std::cout << "Called the copy constructor." << std::endl;
};
// 重载operator=运算符
Cat& operator=(const Cat& cat) {
std::cout << "Called the operator=." << std::endl;
this->breed = cat.breed;
this->weight = cat.weight;
this->age = cat.age;
return *this;
};
private:
// 品种
std::string breed;
float weight;
float age;
};
void Cat::printInfo(void)
{
using namespace std;
cout << "breed: " << breed << endl;
cout << "weight: " << weight << endl;
cout << "age: " << age << endl;
}
int main()
{
// 含参构造
Cat cat1 = Cat("British shorthair cat", 1.0, 0.75);
cat1.printInfo();
// 拷贝构造
Cat cat2 = Cat(cat1);
cat2.printInfo();
// 同样是拷贝构造
Cat cat3 = cat1;
cat3.printInfo();
// 调用赋值运算函数
cat3 = cat1;
return 0;
}
则上述的:
Cat cat2 = Cat(cat1);
Cat cat3 = cat1;
在面相对象的程序设计中,接口可以理解为一个规范或约定。
在C++中并未提供类似于Java中直接给出的 interface
关键字,因此在C++中的接口通常使用抽象类实现。具体的接口可以如下定义:
#include <iostream>
#include <string>
class Animal {
public:
// 纯虚函数,使 Animal 成为抽象类
virtual std::string getName() const = 0; // 获取动物的名字
virtual void setName(const std::string& name) = 0; // 设置动物的名字
virtual void makeSound() = 0; // 发出声音
// 虚析构函数,确保派生类的析构函数被调用
virtual ~Animal() {}
};
在Java中,接口可通过如下方式定义:
public interface Animal {
String getName();
void setName(String name);
void makeSound();
}
在上述的若干语言中的代码均定义了一个最基础的 Animal
"规范"(即接口),均定义有如下方法:
而在Java中,继承有两种方式:
extends
特性。该特性主要用来扩展类的功能。子类继承父类后,除了可以获得父类的属性和方法外,还可以添加新的属性和方法,或者覆盖(Override)父类的方法。implements
特性。当使用该特性时,该类必须实现对应接口的所有方法(除非该类被声明为抽象类)。该特性确保了使用implements的非抽象类能满足对应接口所要求的所有行为规范。多态
(Polymorphism
)是一个希腊词汇,从字面上理解为多种形态。
该特性允许开发者使用相同的接口调用不同的基类方法。
其最重要的两个要素为:
在上述的定义下,从语言理论上,多态通常有如下的分类:
接下来将根据如下的C++代码对如上的若干分类进行讨论:
#include <iostream>
// 基类
class Animal {
public:
// 虚函数
virtual void makeSound() {
std::cout << "Some animal sound" << std::endl;
}
virtual ~Animal() {} // 虚析构函数,确保删除派生类对象时,能正确调用派生类的析构函数
};
// 派生类 Dog
class Dog : public Animal {
public:
void makeSound() override {
std::cout << "Woof" << std::endl;
}
};
// 派生类 Cat
class Cat : public Animal {
public:
void makeSound() override {
std::cout << "Meow" << std::endl;
}
};
// 函数,接受 Animal 类型的引用
void perform(Animal &animal) {
animal.makeSound();
}
int main() {
Dog myDog;
Cat myCat;
myDog.makeSound();
myCat.makeSound();
perform(myDog); // 输出 Woof
perform(myCat); // 输出 Meow
return 0;
}
在上述代码中:
Dog
和 Cat
类继承了 Animal
类,并重写了 makeSound
方法,是类型多态中的包含多态。myDog.makeSound();
和 myCat.makeSound();
:
void perform(Animal &animal);
、 perform(myDog);
以及 perform(myCat);
:
perform
方法允许接收比它在父类中声明的方法更一般的类型作为参数(即允许接收 Dog
和 Cat
类型),属于逆变。正如类的继承关系,通常父类是子类的子集。因此子类转父类通常是自动转换的,该过程是多态的一种特性。这个过程叫做向上转型。
而当父类转子类时,该方法通常需要强制类型转换。而子类通常含有父类所不拥有的特性和方法,因此并不是所有的父类转子类都会成功。通常来说,该转换成功的前提是该父类事实上是子类隐式转换过来的,也就是说前提是这个父类本身就是这个子类。这个过程叫做向下转型。
基本概念: