C++ 面向对象
C++ 面向对象
C++是一名面向对象的语言。每门编程语言都有自己的编程思维,C++语言的编程思维即RAII + OOP.
创建基类Animal
/**
* @author zsh
* 所有类对象的成员方法均是inline方法
* 标准写法是
* hpp: 声明类
* cpp: 实现类
*/
class Animal {
private:
std::string species;
int age;
public:
// 默认构造函数
Animal() : species("单细胞"), age(0){
std::cout << "Animal Default Constructor" << "\n";
}
// 带参构造函数
Animal(std::string species, int age) : species(species), age(age){
std::cout << std::format("Animal Constructor species: {}, age: {}", species, age) << "\n";
}
// 拷贝构造函数
Animal(const Animal& other) : species(other.species), age(other.age){
std::cout << std::format("Animal Copy Constructor species: {}, age: {}", species, age) << "\n";
}
// 移动构造函数
Animal(Animal&& other) noexcept : species(std::move(other.species)), age(std::exchange(other.age, 0)){
std::cout << std::format("Animal Move Constructor species: {}, age: {}", species, age) << "\n";
}
// 析构函数
virtual ~Animal(){
std::cout << std::format("Animal Destructor species: {}, age: {}", species, age) << "\n";
}
// 重写赋值运算符
Animal& operator=(const Animal& other){
if(this != &other){
species = other.species;
age = other.age;
}
std::cout << std::format("Animal Copy Assignment Operator species: {}, age: {}", species, age) << "\n";
return *this;
}
// 移动赋值运算符
Animal& operator=(Animal&& other) noexcept {
if(this != &other){
species = std::move(other.species);
age = std::exchange(other.age, 0);
}
std::cout << std::format("Animal Move Assignment Operator species: {}, age: {}", species, age) << "\n";
return *this;
}
// 重新<<运算符, 友元:访问成员私有变量
friend std::ostream& operator<<(std::ostream& os, const Animal& animal) {
os << std::format("species: {}, age: {}", animal.species, animal.age);
return os;
}
// 定义为全局方法也是可以的
// static std::ostream& operator<<(std::ostream& os, const Animal& animal) {
// os << std::format("species: {}, age: {}", animal.getSpecies(), animal.getAge());
// return os;
// }
/**
* 第一个 const 代表返回不可变的值,一般和引用搭配
* 第二个 const 代表这个函数无法修改this对象的任何属性
*/
const std::string& getSpecies() const {
return species;
}
int getAge() const {
return age;
}
void setSpecies(const std::string species) {
this->species = species;
}
void setAge(const int age) {
this->age = age;
}
// 虚函数,有虚函数,此对象仍然是一个正常可实例化的对象
virtual void makeSound() {
std::cout << "Some generic animal sound" << std::endl;
};
virtual void eat(){
std::cout << "Animal is eating" << std::endl;
}
virtual void sleep(){
std::cout << "Animal is sleeping" << std::endl;
}
};
创建动物类,其有两个属性species、age,代表物种,年龄.
创建Dog类型
class Dog : public Animal {
public:
// 默认构造函数
Dog() : Animal("狗", 0){
std::cout << std::format("Dog Default Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 带参构造函数
Dog(int age) : Animal("狗", age){
std::cout << std::format("Dog Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 拷贝构造
Dog(const Dog& other) : Animal(other.getSpecies(), other.getAge()){
std::cout << std::format("Dog Copy Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 移动构造
Dog(Dog&& other) noexcept : Animal(std::move(other)){ // 1
std::cout << std::format("Dog Move Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 析构函数
~Dog() override {
std::cout << std::format("Dog Destructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 拷贝赋值运算符
Dog& operator=(const Dog& other){
Animal::operator=(other);
std::cout << std::format("Dog Copy Assignment Operator Species: {}, age: {}\n", getSpecies(), getAge());
return *this;
}
// 移动赋值运算符
Dog& operator=(Dog&& other) noexcept {
Animal::operator=(std::move(other)); // 将other转为Animal&&,匹配父类移动赋值
std::cout << std::format("Dog Move Assignment Operator species: {}, age: {}", getSpecies(), getAge()) << "\n";
return *this;
}
// 狗叫
void makeSound() override {
std::cout << "Woof! Woof!" << std::endl;
}
};
代码1处的noexcept代码可以促使编译器优化,使得移动优先于拷贝.
想想数组,如果是拷贝那就得重新分配空间和赋值内容到新空间,而移动只需要将新数组的指针指向旧数组,旧数组指向null,性能嘎嘎好,所以move应该优先于copy.
注意搞清楚move和copy,不要混用两者!!!
继承访问修饰符(inheritance access specifier)。
继承后面可以跟public、protected、private,class默认private, struct默认public.
总结表格
三种权限修饰符修饰后,外部访问情况。
| 基类成员权限 | public 继承后 | protected 继承后 | private 继承后 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
常用运算符
我们先看下面的代码:
代码1
Animal a1 = Dog(1);
std::cout << a1 << "\n";
/*
运行结果
Animal Constructor species: 狗, age: 1
Dog Constructor species: 狗, age: 1
Animal Move Constructor species: 狗, age: 1
Dog Destructor species: , age: 0
Animal Destructor species: , age: 0
species: 狗, age: 1
Animal Destructor species: 狗, age: 1
*/
这种并非多态,将一个Dog对象切片为Animal然后移动构造处Animal.
代码2
Dog d1;
Dog d2 = d1;
d2.setSpecies("猫");
std::cout << d1 << "\n";
std::cout << d2 << "\n";
/*
运行结果
Animal Constructor species: 狗, age: 0
Dog Default Constructor species: 狗, age: 0
Animal Constructor species: 狗, age: 0
Dog Copy Constructor species: 狗, age: 0
species: 狗, age: 0
species: 猫, age: 0
Dog Destructor species: 猫, age: 0
Animal Destructor species: 猫, age: 0
Dog Destructor species: 狗, age: 0
Animal Destructor species: 狗, age: 0
*/
代码3
触发移动赋值运算
Dog d1;
std::cout << d1 << "\n";
d1 = Dog(2);
std::cout << d1 << "\n";
/*
Animal Constructor species: 狗, age: 0
Dog Default Constructor species: 狗, age: 0
species: 狗, age: 0
Animal Constructor species: 狗, age: 2
Dog Constructor species: 狗, age: 2
Animal Move Assignment Operator species: 狗, age: 2
Dog Move Assignment Operator species: 狗, age: 2
Dog Destructor species: , age: 0
Animal Destructor species: , age: 0
species: 狗, age: 2
Dog Destructor species: 狗, age: 2
Animal Destructor species: 狗, age: 2
*/
多态
触发多态四种方法
// 1
void learn03(){
Animal *a = new Dog(1);
a -> makeSound();
delete a;
}
// 2
Dog d;
Animal &a = d;
a.makeSound();
// 3
Animal &&a = Dog(1);
a.makeSound();
// 4
const Animal &a = Dog(1);
a.makeSound();
多态的条件:
- 基类有虚函数
- 子类重新虚函数
- 基类引用指向子类对象
代码分析:
- 首先在堆创建了一个
Dog对象,然后将Animal的指针指向了Dog的内存,符合多态条件。 - 在栈空间创建了一个
Dog对象,且d引用了此对象,然后a也是引用了Dog所在的内存,符合多态条件。 - 临时对象即右值,他的生命周期是这条语句执行结束,就会被立即回收,所以
&&右值引用完全接管了这个临时对象。符合多态条件。 - 代码
Animal &a = Dog(1);你会发现编译器报错如下cannot bind non-const lvalue reference of type 'Animal&' to an rvalue of type 'Animal',非常量左值不能绑定右值,所以改为常量左值,但是,常量左值只能调用const成员方法,所以,需要将makeSound改为const成员方法才能实现多态。
多态方法
情况1
static void playSound(Animal a) {
a.setSpecies("playSound");
a.makeSound();
}
void learn03(){ // 1
Dog d(1);
playSound(d);
}
void learn03(){ // 2
playSound(Dog(1));
}
/*
运行结果1:
Animal Constructor species: 狗, age: 1
Dog Constructor species: 狗, age: 1
Animal Copy Constructor species: 狗, age: 1
Some generic animal sound
Animal Destructor species: playSound, age: 1
Dog Destructor species: 狗, age: 1
Animal Destructor species: 狗, age: 1
运行结果2:
Animal Constructor species: 狗, age: 1
Dog Constructor species: 狗, age: 1
Animal Move Constructor species: 狗, age: 1
Some generic animal sound
Animal Destructor species: playSound, age: 1
Dog Destructor species: , age: 0
Animal Destructor species: , age: 0
*/
可以从结果看出,只是触发了切片+拷贝,没有触发多态。
情况2:
// 代码1
static void playSound(Animal &&a) {
a.setSpecies("playSound");
a.makeSound();
}
void learn03(){
playSound(Dog(1));
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// 代码2
static void playSound(Animal &a) {
a.setSpecies("playSound");
a.makeSound();
}
void learn03(){
Dog d;
playSound(d);
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// 代码3
static void playSound(const Animal &a) {
a.makeSound();
}
void learn03(){
playSound(Dog(1));
std::this_thread::sleep_for(std::chrono::seconds(1));
}
可以看到多态触发了,和上面的原理一样,就不赘述了。
情况3
static Animal getAnimal() {
Dog d(1);
return d; // 1
}
// 被优化为
static Animal getAnimal() {
return Dog(1); // 1
}
void learn03(){
getAnimal().makeSound();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
/*
执行结果:
Animal Constructor species: 狗, age: 1
Dog Constructor species: 狗, age: 1
Animal Move Constructor species: 狗, age: 1
Dog Destructor species: , age: 0
Animal Destructor species: , age: 0
Some generic animal sound
Animal Destructor species: 狗, age: 1
// wait 1 seconds...
*/
从执行结果可以看出getAnimal返回的Animal对象,Dog对象是一个右值,所以将Dog右值赋值为Animal触发移动构造,打印出移动构造日志,然后调用了右值的makeSound方法,然后立马被释放掉了。
void learn03(){
Animal a = getAnimal(); // 2
a.makeSound();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
则会把getAnimal返回的Animal对象使用移动构造被赋值给了一个左值,其生命周期为这个函数结束其内存被释放。
这里的细节非常之多:
- 代码1出会发生
lvalue->rvalue.所以会调用Move Constructor,所以打印出Animal Move Constructor species: 狗, age: 1 - 2处大多数编译器会启用 复制省略(copy elision),因此不会再额外调用构造函数。
证明
static Animal getAnimal() {
Animal d("猫", 1);
std::cout << d << "\n";
return d;
}
void learn03(){
getAnimal().makeSound();
std::this_thread::sleep_for(std::chrono::seconds(1));
}
上面代码只调用了构造、析构函数。
虚拟继承
/**
* @author zsh
* 所有类对象的成员方法均是inline方法
* 标准写法是
* hpp: 声明类
* cpp: 实现类
*/
class Animal {
private:
std::string species;
int age;
public:
// 默认构造函数
Animal() : species("单细胞"), age(0){
std::cout << "Animal Default Constructor" << "\n";
}
// 带参构造函数
Animal(std::string species, int age) : species(species), age(age){
std::cout << std::format("Animal Constructor species: {}, age: {}", species, age) << "\n";
}
// 拷贝构造函数
Animal(const Animal& other) : species(other.species), age(other.age){
std::cout << std::format("Animal Copy Constructor species: {}, age: {}", species, age) << "\n";
}
// 移动构造函数
Animal(Animal&& other) noexcept : species(std::move(other.species)), age(std::exchange(other.age, 0)){
std::cout << std::format("Animal Move Constructor species: {}, age: {}", species, age) << "\n";
}
// 析构函数
virtual ~Animal(){
std::cout << std::format("Animal Destructor species: {}, age: {}", species, age) << "\n";
}
// 重写赋值运算符
Animal& operator=(const Animal& other){
if(this != &other){
species = other.species;
age = other.age;
}
std::cout << std::format("Animal Copy Assignment Operator species: {}, age: {}", species, age) << "\n";
return *this;
}
// 移动赋值运算符
Animal& operator=(Animal&& other) noexcept {
if(this != &other){
species = std::move(other.species);
age = std::exchange(other.age, 0);
}
std::cout << std::format("Animal Move Assignment Operator species: {}, age: {}", species, age) << "\n";
return *this;
}
// 重新<<运算符, 友元:访问成员私有变量
friend std::ostream& operator<<(std::ostream& os, const Animal& animal) {
os << std::format("species: {}, age: {}", animal.species, animal.age);
return os;
}
// 定义为全局方法也是可以的
// static std::ostream& operator<<(std::ostream& os, const Animal& animal) {
// os << std::format("species: {}, age: {}", animal.getSpecies(), animal.getAge());
// return os;
// }
/**
* 第一个 const 代表返回不可变的值,一般和引用搭配
* 第二个 const 代表这个函数无法修改this对象的任何属性
*/
const std::string& getSpecies() const {
return species;
}
int getAge() const {
return age;
}
void setSpecies(const std::string species) {
this->species = species;
}
void setAge(const int age) {
this->age = age;
}
// 虚函数,有虚函数,此对象仍然是一个正常可实例化的对象
virtual void makeSound() const {
std::cout << "Some generic animal sound" << std::endl;
};
virtual void eat(){
std::cout << "Animal is eating" << std::endl;
}
virtual void sleep(){
std::cout << "Animal is sleeping" << std::endl;
}
};
class Dog : virtual public Animal {
public:
// 默认构造函数
Dog() : Animal("狗", 0){
std::cout << std::format("Dog Default Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 带参构造函数
Dog(int age) : Animal("狗", age){
std::cout << std::format("Dog Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 拷贝构造
Dog(const Dog& other) : Animal(other.getSpecies(), other.getAge()){
std::cout << std::format("Dog Copy Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 移动构造
Dog(Dog&& other) noexcept : Animal(std::move(other)){
std::cout << std::format("Dog Move Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 析构函数
~Dog() override {
std::cout << std::format("Dog Destructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 拷贝赋值运算符
Dog& operator=(const Dog& other){
Animal::operator=(other);
std::cout << std::format("Dog Copy Assignment Operator Species: {}, age: {}\n", getSpecies(), getAge());
return *this;
}
// 移动赋值运算符
Dog& operator=(Dog&& other) noexcept {
Animal::operator=(std::move(other)); // 将other转为Animal&&,匹配父类移动赋值
std::cout << std::format("Dog Move Assignment Operator species: {}, age: {}", getSpecies(), getAge()) << "\n";
return *this;
}
// 狗叫
void makeSound() const override {
std::cout << "Woof! Woof!" << std::endl;
}
};
class Cat : virtual public Animal {
public:
// 默认构造函数
Cat() : Animal("猫", 0){
std::cout << std::format("Cat Default Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 带参构造函数
Cat(int age) : Animal("猫", age){
std::cout << std::format("Cat Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 拷贝构造
Cat(const Cat& other) : Animal(other.getSpecies(), other.getAge()){
std::cout << std::format("Cat Copy Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 移动构造
Cat(Cat&& other) noexcept : Animal(std::move(other)){
std::cout << std::format("Cat Move Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 析构函数
~Cat() override {
std::cout << std::format("Cat Destructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 拷贝赋值运算符
Cat& operator=(const Cat& other){
Animal::operator=(other);
std::cout << std::format("Cat Copy Assignment Operator Species: {}, age: {}\n", getSpecies(), getAge());
return *this;
}
// 移动赋值运算符
Cat& operator=(Cat&& other) noexcept {
Animal::operator=(std::move(other)); // 将other转为Animal&&,匹配父类移动赋值
std::cout << std::format("Cat Move Assignment Operator species: {}, age: {}", getSpecies(), getAge()) << "\n";
return *this;
}
// 猫叫
void makeSound() const override {
std::cout << "Mia O! Mia O!" << std::endl;
}
};
class CatDog : public Dog, public Cat {
public:
// 默认构造函数
CatDog() : Animal("猫狗", 0){
std::cout << std::format("CatDog Default Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 带参构造函数
CatDog(int age) : Animal("猫狗", age){
std::cout << std::format("CatDog Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 拷贝构造
CatDog(const CatDog& other) : Animal(other.getSpecies(), other.getAge()){
std::cout << std::format("CatDog Copy Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 移动构造
CatDog(CatDog&& other) noexcept : Animal(std::move(other)){
std::cout << std::format("CatDog Move Constructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 析构函数
~CatDog() override {
std::cout << std::format("CatDog Destructor species: {}, age: {}\n", getSpecies(), getAge());
}
// 拷贝赋值运算符
CatDog& operator=(const CatDog& other){
Animal::operator=(other);
std::cout << std::format("CatDog Copy Assignment Operator Species: {}, age: {}\n", getSpecies(), getAge());
return *this;
}
// 移动赋值运算符
CatDog& operator=(CatDog&& other) noexcept {
Animal::operator=(std::move(other)); // 将other转为Animal&&,匹配父类移动赋值
std::cout << std::format("CatDog Move Assignment Operator species: {}, age: {}", getSpecies(), getAge()) << "\n";
return *this;
}
// 猫狗叫
void makeSound() const override {
std::cout << "Mia Woof! Mia Woof!" << std::endl;
}
};
namespace learn01{
void learn03(){
CatDog c(1);
std::cout << c << "\n";
}
}
/*
运行结果
Animal Constructor species: 猫狗, age: 1
Dog Default Constructor species: 猫狗, age: 1
Cat Default Constructor species: 猫狗, age: 1
CatDog Constructor species: 猫狗, age: 1
species: 猫狗, age: 1
CatDog Destructor species: 猫狗, age: 1
Cat Destructor species: 猫狗, age: 1
Dog Destructor species: 猫狗, age: 1
Animal Destructor species: 猫狗, age: 1
*/
在没有菱形继承的情况下,无需使用虚拟继承。
如果存在菱形继承的情况下,需要使用虚拟继承。以CatDog为例,虚拟继承保留了CatDog自由实现方法的权利,它可以决定复用Animal、Dog、Cat的成员函数,或者自己重写。

C++ 面向对象
浙公网安备 33010602011771号