8-C++远征之继承篇-学习笔记

C++远征之继承篇

开篇介绍

整个C++远征计划: 起航->离港->封装->继承

mark

  • 为什么要用继承?
  • 为什么要有继承?
  • 如何来定义基类 <—-> 派生类?
  • 基类到派生类的三种继承关系: 公有继承,保护继承,私有继承
  • IS-a & Has a
  • 多重继承 & 多继承 & 虚继承(解决多继承中的一些问题)

为什么继承?

现实生活中: 儿子继承父亲财产

mark

  • 生活中继承 不等于 c++中的继承

为什么要有继承?

从下面的例子说起

人类:

class Person
{
public:
    void eat();
    string m_strName; // 名字
    int m_iAge; // 年龄
}

工人(人类的一种):

class Worker
{
public:
    void eat(); 
    void work(); // 可以工作
    string m_strName;
    int m_iAge; 
    int m_iSalary; // 会发工资
}

代码重用,前提条件是发生关系的这两个类是超集子集的关系。

mark

class Worker: public Person
{
public:
    void work();
    int m_iSalary;
}

此时工人类是人类的派生类。人类是工人类的基类。只需要写自己特有的。

mark

这里有一个配对的概念,提到基类的时候,对应的是派生类。提到父类时,与之对应的是子类。

内存中的对象:

mark

人类如果实例化一个对象,就有两个数据成员。虽然在工人类定义姓名年龄,但因为继承,工人类也包含姓名和年龄。

代码演示

要求:

  • 实例化时先实例化子类,再实例化父类; 而析构函数正好相反。
  • 子类已经同时具有父类的数据成员和成员函数

要求

2-2-InheritWorkerPerson

Person.h:

#include <string>
using namespace std;

class  Person
{
public:
     Person();
    ~ Person();
    void eat();
    string m_strName;
    int m_iAge;
};

Person.cpp:

#include "Person.h"
#include <iostream>

using namespace std;

Person::Person()
{
    cout << "Person()" << endl;

}

Person::~Person()
{
    cout << "~Person()" << endl;

}

void Person::eat() {
    cout << "eat()" << endl;
}

worker.h:

#include "Person.h"

// 要求采用公有继承
class Worker:public Person
{
public:
    Worker();
    ~Worker();
    void work();
    int m_iSalary;
};

worker.cpp:

#include "Worker.h"
#include <iostream>

using namespace std;

Worker::Worker()
{
    cout << "Worker()" << endl;

}
Worker::~Worker()
{
    cout << "~Worker()" << endl;

}

void Worker::work()
{
    cout << "Work" << endl;
}

main.cpp:

#include "Worker.h"
#include <stdlib.h>
#include <iostream>

using namespace std;

int main(void)
{
    // 堆中申请内存
    Worker *p = new Worker();
    p->m_strName = "mtianyan";
    p->m_iAge = 21;
    p->eat();
    p->m_iSalary = 10000;
    p->work();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

mark

可以看到实例化worker的时候,先实例化出爸爸Person,然后生出儿子Worker。
销毁时,先销毁儿子Worker,再销毁掉了Person。

main.cpp

#include "Worker.h"
#include <stdlib.h>
#include <iostream>

using namespace std;

int main(void)
{
    // 栈中申请内存
    Worker worker;
    worker.m_strName = "Jim";
    worker.m_iAge = 10;
    worker.eat();
    worker.m_iSalary = 1000;
    worker.work();
    system("pause");
    return 0;
}

mark

栈中申请内存,也是先有爸爸,然后生儿子。 销毁时,儿子先走,爸爸后走。

C++继承方式

  • 公有继承: class A:public B
  • 保护继承: class A:protected B
  • 私有继承: class A:private B

公有继承

class  Person
{
public:
    void eat();
    string m_strName;
    int m_iAge;
};
class Worker:public Person
{
public:
    // void eat(); 
    void work();
    // int m_iAge; 
    // string m_strName;
    int m_iSalary;
};

注释的是不用写,但是已经包含在Worker中。

int main(void)
{
    Worker worker;
    worker.m_strName = "Merry";
    worker.eat();

    return 0;
}

使用时,Worker可以调用父类的数据成员和成员函数。

Protected 和 private 不涉及继承时,他们两是一样的。

private和protected在继承时的区别:

  • 子类通过protect继承了父类的时候,子类可以通过自己来访问父类的数据成员。
  • 子类通过private继承了父类的时候,子类不能通过自己来访问父类的数据成员,父类的数据成员只有通过自己才能访问。

将刚才的例子进行修改:

class Person
{
public:
    void eat();
protected:
    int m_iAge;
private:
    string m_strName;
};
int main()
{
    Person person;
    person.eat(); //Public, 必须正确
    person.m_iAge = 20; //Protected,禁止访问
    person.m_strName = "jim"; //private,禁止访问
    return 0;
}

但是实现成员函数时。我们的私有,保护数据成员也是可以正常访问的。

void Person::eat()
{
    m_strName = "jim";
    m_iAge = 10;
}

但当protected遇到继承

class  Person
{
public:
    void eat();
protected:
    string m_strName;
    int m_iAge; 
};

class Worker:public Person
{
public:
    // void eat();
    void work(){m_iAge = 20;}; //继承下来之后可以通过work访问到age
protected:
    // int m_iAge;
    // string m_strName;
    int m_iSalary;
};

公有继承时,public 的继承到public。 protected的继承到protected。

class  Person
{
public:
    void eat();
private:
    string m_strName;
    int m_iAge;
};

class Worker:public Person
{
public:
    // void eat();
    void work(){m_iAge = 20;};
private:
    int m_iSalary;
};

public的继承到public。父类中的private被继承到了不可见位置。
而不是private部分。所以,此时通过work操作父类的m_iage会出错。

公有继承

公有继承代码示例

要求:

要求

3-2-PublicInheritWorkerPerson

初始化时代码与上次示例代码保持一致,下面开始进行修改。

  1. 将堆中实例化的对象改用栈中实例化方式。

main.cpp:

#include "Worker.h"
#include <stdlib.h>
#include <iostream>

using namespace std;

int main(void)
{
    // 栈中申请内存
    Worker worker;
    worker.m_strName = "Jim";
    worker.m_iAge = 10;
    worker.eat();
    worker.m_iSalary = 1000;
    worker.work();
    system("pause");
    return 0;
}

mark

可以看到运行结果没有发生变化,依然可以从子类调用父类的成员函数,数据成员。

  • 实验二: 修改Person.h中数据成员
protected:
    string m_strName;
private:
    int m_iAge;

Person.h:

#include <string>
using namespace std;

class  Person
{
public:
     Person();
    ~ Person();
    void eat();
protected:
    string m_strName;
private:
    int m_iAge;
};

Person.cpp中修改eat方法,让其访问protected和private下的数据成员。

#include "Person.h"
#include <iostream>

using namespace std;

Person::Person()
{
    cout << "Person()" << endl;

}

Person::~Person()
{
    cout << "~Person()" << endl;

}

void Person::eat() {
    m_strName = "mtianyan";
    m_iAge = 21;
    cout << "eat()" << endl;
}

main.cpp将Person.h引入。

#include <stdlib.h>
#include <iostream>
#include "Person.h"

using namespace std;

int main(void)
{
    Person person;
    person.eat(); // 可以正常访问
    // person.m_strName = "mtianyan666"; //无法访问 protected 成员
    // person.m_iAge = 21; //无法访问 private 成员

    system("pause");
    return 0;
}

类自己的public成员函数,函数内部可以访问到类自身的保护以及私有成员变量。

而类内受保护的私有成员,保护成员;无法在类外直接被访问。

公有继承之后父类的私有数据成员被子类继承到不可见位置,无法正常使用。

父类中的protected成员,子类也可以正常访问。被放在了子类的protected部分。

将m_strName,m_iAge放在protected下。

class  Person
{
public:
     Person();
    ~ Person();
    void eat();
protected:
    string m_strName;
    int m_iAge;
};

在子类中访问父类的protected数据成员。

void Worker::work()
{
    m_iAge = 21;
    m_strName = "mtianyan";
    cout << "Work" << endl;
}
int main(void)
{
    Worker worker;
    worker.work();

    system("pause");
    return 0;
}

mark

结果:正常运行。

  • 父类的private数据成员父类是否可以正常访问:不可以

将上面代码中person下的protected改为private

则会报错:

Person::m_iAge”: 无法访问 private 成员(在“Person”类中声明)

c++保护继承,私有继承

  • 公有继承: class A:public B
  • 保护继承: class A:protected B
  • 私有继承: class A:private B

公有继承

保护继承

私有继承

Private继承过来,会将父类的public和protected都降级为子类的private

例子:

class Line{
public:
    Line(int x1,int y1, int x2,int y2);
private:
    Coordinate m_coorA; //线段只能访问到m_coorA的公有数据成员和公有成员函数
    Coordinate m_coorB;
}

在私有继承中,子类也只能访问父类的公有数据成员和公有成员函数。

  • 线段和坐标的关系: has-a(包含关系)
  • 私有继承(包含)

因为子类对象包含父类: 只能访问父类当中公有数据成员和成员函数。

保护与私有继承代码演示

要求:

要求

protected继承:

public成员可以一直protected继承下去,被继承后属于protected,不可以被对象直接访问;可以被类自身的成员函数访问。

protected成员可以一直protected继承下去,被继承后属于protected,不可以被对象直接访问;

private成员不能被protected继承。

3-4-ProtectedPrivateInherit

人类派生出军人,军人派生出步兵。

Person.h:

#include <string>
using namespace std;

class  Person
{
public:
     Person();
     void play();

protected:
    string m_strName;
};

Person.cpp:

#include <iostream>
#include "Person.h"
using namespace std;

Person::Person()
{
    m_strName = "merry";
}

void Person::play() {
    cout << "person - play" << endl;
    cout << m_strName << endl;
}

Soldier.h:

#include "Person.h"

class  Soldier:public Person
{
public:
     Soldier();
     void work();

private:
    int m_iAge;
};

Soldier.cpp:

#include "Soldier.h"
#include <iostream>
using namespace std;

Soldier::Soldier()
{
}

void Soldier::work()
{
    // 访问基类(人类)的m_strName
    m_strName = "JIm";
    m_iAge = 20;
    cout << m_strName << endl;
    cout << m_iAge << endl;
    cout << "soldier - work" << endl;
}

Infantry.h:

#include "Soldier.h"

class Infantry:public Soldier {
public:
    void attack();
};

Infantry.cpp:

#include <iostream>
#include "Infantry.h"

using namespace std;

void Infantry::attack() {
    cout << "Infantry --attack" << endl;
}

main.cpp:

#include "Soldier.h"
#include <stdlib.h>
#include <iostream>

int main()
{
    Soldier soldier;
    // work会间接的访问到基类person中的m_strname
    soldier.work();
    // 进行了公有继承,父类的protected数据成员会被继承到子类的protected下面
    // 父类的public也会继承到子类的public下面
    soldier.play();//调用父类的成员函数
    system("pause");
    return 0;
}

运行结果:

mark

将soldier继承Person的方式改为protected继承:

class  Soldier :protected Person
{ //略 };

如果slodier保护继承了person,那么person下的public和protected数据成员和成员函数都会被继承到solider的protect部分。

这意味着通过子类对象只能直接访问到自己的public下的成员函数与数据成员。

main.cpp:

int main()
{
    Soldier soldier;
    soldier.work(); // work是自己public下的,正常访问
    soldier.play(); // play继承过来被放在了protected,无法直接访问了。 
    system("pause");
    return 0;
}

报错:

错误  C2247   “Person::play”不可访问,因为“Soldier”使用“protected”从“Person”继承  

因此此时无法直接访问继承过来的protected下的play(),只有两种可能,一种根本没有继承过来,,一种是继承过来无法访问。如何证明继承过来了呢?

此时通过一个孙子类(Infantry)来公共继承soldier则可以在它自身的成员函数attack()中访问到继承来的Person的数据成员以及成员函数。

其实这时候在儿子类(Soldier)中新增一个成员函数,只要能使用protected中的父类数据成员和成员函数也是可以证明的。

验证方法1:(步兵类中attack()调用Person下数据成员)

Infantry.cpp:

#include <iostream>
#include "Infantry.h"

using namespace std;

void Infantry::attack() {
    m_strName = "Mtianyan";
    cout << m_strName << endl;
    cout << "Infantry --attack" << endl;
}

main.cpp:

#include <stdlib.h>
#include <iostream>
#include "Infantry.h"

int main()
{   
    Infantry infantry;
    infantry.attack();

    system("pause");
    return 0;
}

mark

可以看到attack可以访问到继承过来的protected的数据成员。

验证方法2(不严谨),Soldier自身的成员函数来访问Person的Protected数据成员

因为继承到private,也可以被自身成员函数访问。

// Person.h
protected:
    string m_strName;

// Soldier.cpp(自身的work方法)

void Soldier::work()
{
    m_strName = "JIm";
    cout << m_strName << endl;
    cout << "soldier - work" << endl;
}

private继承方式实验

Solder.h:

class  Soldier :private Person
{};

Person中的public和protected都会被继承到Soldier的private下。

这时在Soldier中可以直接访问这些数据成员。但孙子就拿不到爸爸从爷爷那私有继承的数据成员了。

实验证明:

错误  C2248   “Person::m_strName”了: 无法访问 无法访问的 成员(在“Person”类中声明)

在孙子辈的步兵类中已经无法访问到爷爷的m_strName”了

mtianyan: B类从A类派生,那么B类中含有A类的所有数据成员(私有的也被继承过来了,只不过继承到了不可见位置,无法访问)

B类从A类公共派生,那么可以在B类中直接使用A的公共及保护限定符的数据成员,不能使用私有成员。

B类从A类公共派生,那么A类的私有成员函数不能被B类继承并使用。(会继承下来,但是不能使用,所以正确)

B类从A类私有派生,那么A类的公共成员函数成为B类的私有成员函数。

巩固练习

定义两个类,人类中含有数据成员姓名(m_strName)及成员函数eat()
士兵类从人类派生,含有数据成员编号(m_strCode)及成员函数attack()
在main函数通过对数据的访问,体会公有继承的语法特点。

#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;

/**
 * 定义人的类: Person
 * 数据成员姓名: m_strName
 * 成员函数: eat()
 */
class Person
{
public:
    string m_strName;
    void eat()
    {
        cout << "eat" << endl;
    }
};

/**
 * 定义士兵类: Soldier
 * 士兵类公有继承人类: public
 * 数据成员编号: m_strCode
 * 成员函数: attack()
 */
class Soldier:public Person
{
public:
    string m_strCode;
    void attack()
    {
        cout << "fire!!!" << endl;
    }
};

int main(void)
{
    // 创建Soldier对象
    Soldier soldier;
    // 给对象属性赋值
    soldier.m_strName = "mtianyan";
    soldier.m_strCode = "273";
    // 打印对象属性值
    cout << soldier.m_strName << endl;
    cout << soldier.m_strCode << endl;
    // 调用对象方法
    soldier.attack();
    soldier.eat();

    return 0;
}

mark

c++隐藏

覆盖 <-> 隐藏,很容易混淆.

本次课程重点介绍隐藏。

什么是隐藏?

隐藏

  • 父子两代(子类公有继承父类),当都有同名函数。子类会将父类的函数隐藏。
  • 表现: 实例化B类对象时,只能直接访问到b中的abc方法。

  • 但是实际上父类的abc方法只是隐藏了起来,因为确实是被继承过来了,通过特殊手段还可以访问到。

  • 同名数据成员和成员函数都具有隐藏性质。

父子关系 & 成员同名 & 隐藏

代码:

class Person
{
public:
    void play();

protected:
    string m_strName;
};

// 父子关系
class Soldier:public Person
{
public:
    void play(); // 同名成员函数
    void work();
protected:
    int m_iCode;
}

调用示例代码:

int main()
{
    Soldier soldier;
    soldier.play(); //调用到soldier自己的play
    soldier.Person::play(); //可以调用到父类人的play

    return 0;
}

数据成员同名:

数据成员同名

void Soldier::work()
{
    code = 1234;
    Person::code = "5678";//访问到的是父类的数据成员
}

可以通过比较好的命名方法(m_strCode,m_iCode)是可以避免重名的。

隐藏编码实例(一)

要求

4-2-HideMemberFunctionVariable

程序代码

Person.h

#include <string>
using namespace std;

class Person
{
public:
    Person();
    void play();
protected:
    string m_strName;
};

Person.cpp:

#include "Person.h"
#include <iostream>
using namespace std;

Person::Person()
{
    m_strName = "mtianyan";

}

void Person::play()
{
    cout << "person - play()" << endl;
    cout << m_strName << endl;
}

Soldier.h

#include "Person.h"

class  Soldier:public Person
{
public:
     Soldier();
     void play();
     void work();

protected:
};

Soldier.cpp

#include "Soldier.h"
#include <iostream>
using namespace std;

Soldier::Soldier()
{}
void Soldier::play() {
    cout << "soldier - play()" << endl;
}

void Soldier::work() {
    cout << "soldier - work()" << endl;
}

main.cpp:

#include <iostream>
#include <stdlib.h>
#include "Soldier.h"

int main()
{
    Soldier soldier;
    soldier.play();
    soldier.work();
    soldier.Person::play();
    system("pause");
    return 0;
}

mark

使用方法名直接调用的是soldier自己的。

  • ""在程序目录下找
  • <>在程序默认库查找 - 右键打开文档可以打开
  • 使用string类型必须#include<string>时,需要配套使用using namespace std;才可正常使用string,否则会出错,也就是string类型也是在std命名空间的。
  • 继承只有一个冒号。而指明类的方法有两个。
  • 声明类的最后要加分号。

隐藏编码二

如果想要打印Person的。则需要soldier.Person::play();

可以调用到person下的play方法。

当前不仅同名而且参数相同。如果参数不同,还会隐藏吗?

是的即使参数不同,也不会形成重载,只是隐藏。

// 不修改
void Person::play()
{
    cout << "person - play()" << endl;
    cout << m_strName << endl;
}
// Soldier.h添加参数
    void play(int x);
// Soldier.cpp中添加参数
void Soldier::play(int x) {
    cout << "soldier - play()" << endl;
}

// 调用时
int main()
{
    Soldier soldier;
    soldier.play(7); // 调用Soldier类自己的有参play
    soldier.work();
    soldier.play(); //虽然与父类的参数要求一致,但当前父类方法被隐藏,无法调用。
    //soldier.Person::play();
    system("pause");
    return 0;
}

报错:

error C2660: “Soldier::play”: 函数不接受 0 个参数

说明即使参数不同。父类和子类的同名函数也不形成重载。而是隐藏。

数据成员重名

给Soldier.h protected下添加一个参数m_strName,使得它拥有一个和Person同名的数据成员

#include "Person.h"

class  Soldier:public Person
{
public:
     Soldier();
     void play(int x);
     void work();

protected:
    string m_strName;
};

Soldier.cpp:

#include "Soldier.h"
#include <iostream>
using namespace std;

Soldier::Soldier()
{

}
// 添加两行打印
void Soldier::play(int x) {
    cout << m_strName << endl;//打印出的是soldier下的
    cout << Person::m_strName << endl;//打印父类的
    cout << "soldier - play()" << endl;

}

// 添加两行数据成员的访问
void Soldier::work() {
    m_strName = "solider"; //只能赋值给soldier下的m_strname
    Person::m_strName = "Person";
    cout << "soldier - work()" << endl;
}

父子之间的同名数据成员。work中直接访问,只能访问到soldier下的m_strname。

#include <iostream>
#include <stdlib.h>
#include "Soldier.h"
int main()
{
    Soldier soldier;
    soldier.work();
    soldier.play(7);
    soldier.Person::play();
    system("pause");
    return 0;
}

mark

可以看到数据成员被隐藏。

isA

隐形眼镜也是眼镜 - Is-a

  • 眼镜: 基类
  • 隐形眼镜: 派生类

工人士兵都是人的对象

基于上面的结论,示例程序

int main()
{
    Soldier s1;
    Person p1 = s1; //用s1区实例化p1.这样做在语法上是正确的。士兵也是人。

    Person *p2 = &s1; //正确

    s1 = p1; //人赋值给士兵,错误。
    Soldier *s2 = &p1; //士兵指针指向人对象,错误
    return 0;
}

子类的对象可以赋值给父类。也可以用基类的指针指向派生类的对象。

将基类的指针或者引用作为函数参数来使它可以接收传入的子类对象,并且也可以传入基类的对象。

基类作为参数

fun1和fun2都可以传入Person 或 Soldier的对象。

  • fun1要求传入的参数是指针,所以传入 &p1对象p1的地址。
  • 而fun2要求传入的是对象p的引用, 所以可以直接传入。

  • 基类指针指向派生类对象: Person *p = &soldier;

  • 派生类对象初始化基类对象: Person p1 = soldier;

存储结构:

子类父类内存

子类中有父类继承下来的数据成员.也有它自身的数据成员。

当通过子类初始化父类时,会将继承下来的数据成员复制。其他子类自有的截断丢弃。

父类指针指向子类对象,父类也只能访问到自己遗传下去的,无法访问到子类自有的。

Is-a 编码

要求

4-5-SoldierIsAPerson

Person.h:

#include <string>
using namespace std;

class Person
{
public:
    Person(string name = "Person_jim");
    virtual ~Person();// 虚析构函数,可继承。soldier内的也会是虚的。
    void play();
protected:
    string m_strName;
};

Person.cpp:

#include "Person.h"
#include <iostream>
#include <string>
using namespace std;

Person::Person(string name)
{
    m_strName = name;
    cout << "person()" << endl;

}
Person::~Person()
{
    cout << "~person()" << endl;
}

void Person::play()
{
    cout << "person - play()" << endl;
    cout << m_strName << endl;
}

Soldier.h:

#include "Person.h"
#include <string>
using namespace std;

class  Soldier:public Person
{
public:
     Soldier(string name = "Soldier_james",int age =20);
     virtual ~Soldier();
     void work();

protected:
    string m_iAge;
};

Soldier.cpp:

#include "Soldier.h"
#include <iostream>
using namespace std;

Soldier::Soldier(string name,int age)
{
    m_strName = name;
    m_iAge  = age;
    cout << "Soldier()" << endl;
 }

Soldier::~Soldier() {
    cout << "~Soldier()" << endl;
}
void Soldier::work() {
    cout << m_strName << endl;
    cout << m_iAge << endl;
    cout << "Soldier -- work" << endl;
}

main.cpp:

#include <iostream>
#include <stdlib.h>
#include "Soldier.h"

int main()
{
    Soldier soldier;
    Person p = soldier; 

    p.play();

    system("pause");
    return 0;
}

mark

可以看到此时调用Person的play(),打印出的m_strName是Solder的数据成员。

int main()
{
    Soldier soldier;
    Person p; 

    p.play();

    system("pause");
    return 0;
}

mark

因为Person是有默认构造函数的,因此此时打印出来的必定似乎Person中数据成员。

刚才我们是使用soldier来初始化p,现在我们尝试使用赋值方式。

int main()
{
    Soldier soldier;
    Person p;
    p = soldier;
    p.play();
    system("pause");
    return 0;
}

mark

无论是初始化,还是赋值,都可以实现。下面我们试试指针方式。

int main()
{
    Soldier soldier;
    Person *p;
    p = &soldier;
    p->play();
    system("pause");
    return 0;
}

mark

使用父类的指针,调用子类独有的函数

p->work();

会提示错误:

错误  C2039   “work”: 不是“Person”的成员
  • 关于销毁时,析构函数是如何执行的。
int main()
{
    Person *p = new Soldier;

    p->play();

    delete p;
    p=NULL;
    system("pause");
    return 0;
}

mark

可以看到:1. 实例化时先执行父类的构造,再执行子类的构造。 父类指针指向子类对象,访问到的是子类的数据成员。 2. 销毁时先执行子类析构函数,再执行父类析构函数。

执行结果如上图所示,是因为我们的Person.h中已经将析构函数添加了virtual关键字

virtual ~Person();// 虚析构函数,可继承。soldier内的也会是虚的。

如果没有virtual,那么在销毁时会造成只执行了父类的析构函数,没有执行子类的。

mark

新知识点: 虚析构函数

什么时候会用到:

虚析构函数是为了解决基类的指针指向堆中的派生类对象,希望使用基类的指针释放内存。

这个关键字是会被继承下去的,即使在Soldier中不写,也会是一个虚析构函数

is-A 编码二

要求

4-6-SoldierIsAPerson2:

main.cpp:

#include <iostream>
#include <stdlib.h>
#include "Soldier.h"

void test1(Person p)
{
    p.play();
}
void test2(Person &p)
{
    p.play();
}
void test3(Person *p)
{
    p->play();
}

int main()
{
    Person p;
    Soldier s;

    test1(p);
    test1(s);

    system("pause");
    return 0;
}

mark

运行结果前三行是实例化了Person和Soldier的输出。
test1中调用了传入的Person的play方法,打印处理Person_jim。
test1传入s,调用了Person的play方法,数据成员是Soldier的。

销毁了两次Person,是因为调用时,有一个临时的对象person

两次析构函数

两次析构函数是在两个test1执行完之后自动执行的。因为此时传入一个临时变量p。用完就要销毁掉。

    test2(p);
    test2(s);

运行结果:

test2:引用中间没有中间变量的销毁

因为传入的是引用。所以里面调用的仍是传入的对象本身。没有实例化临时变量。

    test3(&p);
    test3(&s);

与test2实验结果完全一致。p分别调用基类和派生类的play

结论:test2 和 test3 不会产生新的临时变量,效率更高。

巩固练习

定义两个类,基类是人类,定义数据成员姓名(name),及成员函数void attack()。
士兵类从人类派生,定义与人类同名的数据成员姓名(name)和成员函数void attack()。
通过对同名数据成员及成员函数的访问理解成员隐藏的概念及访问数据的方法。

#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;

/**
 * 定义人类: Person
 * 数据成员: m_strName
 * 成员函数: attack()
 */
class Person
{
public:
    string m_strName;
    void attack()
    {
        cout << "attack" << endl;
    }
};

/**
 * 定义士兵类: Soldier
 * 士兵类公有继承人类
 * 数据成员: m_strName
 * 成员函数: attack()
 */
class Soldier:public Person
{
public:
    string m_strName;
    void attack()
    {
        cout << "fire!!!" << endl;
    }
};

int main(void)
{
    // 实例士兵对象
    Soldier soldier;
    // 向士兵属性赋值"tomato"
    soldier.m_strName = "tomato";
    // 通过士兵对象向人类属性赋值"Jim"
    soldier.Person::m_strName = "Jim";
    // 打印士兵对象的属性值
    cout << soldier.m_strName << endl;
    // 通过士兵对象打印人类属性值
    cout << soldier.Person::m_strName << endl;
    // 调用士兵对象方法
    soldier.attack();
    // 通过士兵对象调用人类方法
    soldier.Person::attack();

    return 0;
}

mark

多继承与多重继承

多重继承:

多重继承

多重继承-Is-a关系

上述关系具体到代码上可以这样写:

class Person
{
};
class Soldier: public Person
{
};
class Infantryman: public Soldier
{
}; 

多继承:

多继承-一个儿子有两个爸爸

多继承-isa-但是农民和工人没什么关系

具体到代码层面,如下:

class Worker
{
};
class Farmer
{
};
class MigrantWorker: public Worker,public Farmer
{
};

多继承时中间要加逗号,并且要写清楚继承方式。
如果不写,那么系统默认为private继承

多重继承代码演示

多重继承要求

Person -> Soldier -> Infantryman
  • 孙子实例化时先实例化爷爷,然后实例化爸爸。最后才能实例化孙子。

孙子-爸爸-爷爷的生死

  • 孙子先死。然后爸爸死。最后爷爷死。

附录代码 5-2-Multi-Inherit:

Person.h

#include <string>
using namespace std;

class Person
{
public:
    Person(string name = "jim");
    virtual ~Person();// 虚析构函数,可继承。soldier内的也会是虚的。
    void play();
protected:
    string m_strName;
};

Person.cpp

#include "Person.h"
#include <iostream>
#include <string>
using namespace std;

Person::Person(string name)
{
    m_strName = name;
    cout << "person()" << endl;

}
Person::~Person()
{
    cout << "~person()" << endl;
}

void Person::play()
{
    cout << "person - play()" << endl;
    cout << m_strName << endl;
}

Soldier.h:

#include "Person.h"
#include <string>
using namespace std;

class  Soldier :public Person
{
public:
    Soldier(string name = "james", int age = 20);
    virtual ~Soldier();
    void work();

protected:
    string m_iAge;
};

Soldier.cpp:

#include "Soldier.h"
#include <iostream>
using namespace std;

Soldier::Soldier(string name, int age)
{
    m_strName = name;
    m_iAge = age;
    cout << "Soldier()" << endl;
}

Soldier::~Soldier() {
    cout << "~Soldier()" << endl;
}
void Soldier::work() {
    cout << m_strName << endl;
    cout << m_iAge << endl;
    cout << "Soldier -- work" << endl;
}

Infantry.h:

#include "Soldier.h"

class Infantry:public Soldier {
public:
    Infantry(string name = "jack", int age = 30);
    ~Infantry();
    void attack();
};

Infantry.cpp

#include <iostream>
#include "Infantry.h"

using namespace std;
Infantry::Infantry(string name /* = "jack" */, int age /* = 30 */)
{
    m_strName = name;
    m_iAge = age;
    cout << "Infantry()" << endl;
}
Infantry::~Infantry()
{
    cout << "~Infantry()" << endl;
}
void Infantry::attack() {
    cout << m_iAge << endl;
    cout << m_strName<< endl;
    cout << "Infantry --attack" << endl;
}

main.cpp:

#include <iostream>
#include <stdlib.h>
#include "Infantry.h"

void test1(Person p)
{
    p.play();
}
void test2(Person &p)
{
    p.play();
}
void test3(Person *p)
{
    p->play();
}

int main()
{
    Infantry infantry;

    system("pause");
    return 0;
}

mark

可以看到,如我们预期的,先生爷爷,再生爸爸,最后生儿子。

步兵 IsA 军人 IsA 人类

具有传递关系。

int main()
{
    Infantry infantry;
    test1(infantry);
    test2(infantry);
    test3(&infantry);
    system("pause");
    return 0;
}

mark

test1传入的是对象。所以会有临时生成的对象。然后销毁。

c++多继承

多继承的要求

实例化两个父类的顺序与继承时冒号后顺序一致而与初始化列表顺序无关。

class  MigrantWorker:public Worker, public Farmer
  • 是按继承的声明顺序来构造超类的 不是按初始化列表的顺序
  • 函数参数默认值最好在声明时设置而不是在定义时。是因为定义出现在调用后 导致编译其无法识别 然后报错

多继承-实例化与销毁

完整代码 5-3-Multi-TwoInherit:

Worker.h:

#include <string>
using namespace std;

class Worker
{
public:
    Worker(string code ="001");
    virtual ~Worker();
    void carry();
protected:
    string m_strCode;
};

Worker.cpp

#include "Worker.h"
#include <iostream>
using namespace std;
Worker::Worker(string code)
{
    m_strCode = code;
    cout << "worker()" << endl;
}

Worker::~Worker()
{
    cout << "~worker" << endl;
}
void Worker::carry()
{
    cout << m_strCode << endl;
    cout << "worker -- carry()" << endl;
}

Farmer.h

#include <string>
using namespace std;

class Farmer
{
public:
    Farmer(string name = "jack");
    virtual ~Farmer();
    void sow();
protected:
    string m_strName;
};

Farmer.cpp

#include "Farmer.h"
#include <iostream>
using namespace std;

Farmer::Farmer(string name)
{
    m_strName = name;
    cout << "Farmer()" << endl;
}

Farmer::~Farmer()
{
    cout << "~Farmer()" << endl;
}

void Farmer::sow()
{
    cout << m_strName << endl;
    cout << "sow()" << endl;
}

MigrantWorker.h

#include <string>
#include "Farmer.h"
#include "Worker.h"
using namespace std;

class  MigrantWorker:public Worker, public Farmer//此处顺序决定实例化顺序。
{
public:
     MigrantWorker(string name,string code);
     ~ MigrantWorker();
private:
};

MigrantWorker.cpp

#include "MigrantWorker.h"
#include <iostream>
using namespace std;

MigrantWorker::MigrantWorker(string name,string code): Farmer(name), Worker(code)
{
    cout <<":MigrantWorker()" << endl;
}

MigrantWorker::~MigrantWorker()
{
    cout << "~MigrantWorker()" << endl;
}

重点代码:

MigrantWorker::MigrantWorker(string name,string code): Farmer(name), Worker(code)

将其中的name传给Farmer code传给Worker

main.cpp

#include <iostream>
#include "MigrantWorker.h"
#include <stdlib.h>

int main()
{
    // 堆方式实例化农民工对象
    MigrantWorker *p = new MigrantWorker("jim","100");

    p->carry(); // 工人成员函数
    p->sow(); // 农民成员函数
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

mark

  • 可以用指向子类的指针p,调用他两个爸爸的方法。
  • 实例化顺序与声明顺序一致,声明时先写的Worker。
  • 销毁顺序与实例化正好相反

基类不需要定义虚析构函数,虚析构函数是在父类指针指向子类对象的时候使用的。 这里只是简单的实例化子类对象而已,销毁的时候会执行父类和子类的析构函数的

  • 虚析构函数是为了解决基类的指针指向堆中的派生类对象,并用基类的指针删除派生类对象。

巩固练习

  • 定义worker工人类及children儿童类
  • worker类中定义数据成员m_strName姓名
  • children类中定义成员m_iAge年龄
  • 定义ChildLabourer童工类,公有继承工人类和儿童类
  • 在main函数中通过new实例化ChildLabourer类的对象,并通过该对象调用worker及children类中的成员函数,最后销毁该对象,体会多继承的继承特性及构造函数及析构函数的执行顺序。
#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;

/**
 * 定义工人类: Worker
 * 数据成员: m_strName
 * 成员函数: work()
 */
class Worker
{
public:
    Worker(string name)
    {
        m_strName = name;
        cout << "Worker" << endl;
    }
    ~Worker()
    {
        cout << "~Worker" << endl;
    }
    void work()
    {
        cout << m_strName << endl;
        cout << "work" << endl;
    }
protected:
    string m_strName;
};

/**
 * 定义儿童类: Children
 * 数据成员: m_iAge
 * 成员函数: play()
 */
class Children
{
public:
    Children(int age)
    {
        m_iAge = age;
        cout << "Children" << endl;
    }   
    ~Children()
    {
        cout << "~Children" << endl;
    }   
    void play()
    {
        cout << m_iAge << endl;
        cout << "play" << endl;
    }
protected:
    int m_iAge;
};

/**
 * 定义童工类: ChildLabourer
 * 公有继承工人类和儿童类
 */
class ChildLabourer : public Children, public Worker
{
public:
    ChildLabourer(string name, int age):Worker(name),Children(age)
    {
        cout << "ChildLabourer" << endl;
    }

    ~ChildLabourer()
    {
        cout << "~ChildLabourer" << endl;
    }   
};

int main(void)
{
    // 使用new关键字创建童工类对象
    ChildLabourer *p = new ChildLabourer("jim",12);
    // 通过童工对象调用父类的work()和play()方法
    p-> work();
    p->play();
    // 释放
    delete p;
    p = NULL;

    return 0;
}

mark

c++虚继承(理论)

多继承 & 多重继承的烦恼

  • 多继承和多重继承会出现问题呢?

多继承与多重继续的钻石问题(菱形继承)

菱形继承中既有多继承也有多重继承。D中将含有两个完全一样的A的数据。

钻石

如图,假设类a是父类,b类和c类都继承了a类,而d类又继承了b和c,那么由于d类进行了两次多重继承a类,就会出现两份相同的a的数据成员或成员函数,就会出现代码冗余。

-> 农民 | 工人-> 农民工

如何避免该情况的发生,就可以使用虚继承

虚继承是一种继承方式,关键字是virtual

class Worker: virtual public Person // 等价于 public virtual Person
{};
class Farmer: virtual public Person // 等价于 public virtual Person
{};
class MigrantWorker: public Worker,public Farmer
{};

使用虚继承。那么农民工类将只有一份继承到的Person成员。

虚继承编码(一)

虚继承要求

我们在Farmer中和worker中都引入了Person.h,当MigrantWorker继承自他们两。会引入两个Person的数据成员等。

  • 通过宏定义解决重定义:

我们在被公共继承的类中应该这样写:

#ifndef PERSON_H//假如没有定义
#define PERSON_H//定义

//代码

#endif //结束符

附录完整代码: 6-2-VirtualInherit

Person.h:

#ifndef PERSON_H//假如没有定义
#define PERSON_H//定义

#include <string>
using namespace std;

class Person
{
public:
    Person(string color = "blue");
    virtual ~Person();
    void printColor();
protected:
    string m_strColor;
};

#endif //结束符

Person.cpp:

#include <iostream>
#include "Person.h"
using namespace std;

Person::Person(string color)
{
    m_strColor = color;
    cout << "person()" << endl;
}

Person::~Person()
{
    cout << "~Person()" << endl;
}

void Person::printColor()
{
    cout << m_strColor << endl;
    cout << "Person -- printColor" << endl;
}

Worker.h

#include <string>
using namespace std;
#include "Person.h"
class Worker:public Person
{
public:
    Worker(string code ="001",string color ="red");
    // 希望worker可以传入肤色给person
    virtual ~Worker();
    void carry();
protected:
    string m_strCode;
};

Worker.cpp

#include "Worker.h"
#include <iostream>
using namespace std;
Worker::Worker(string code,string color):Person(color)
{
    m_strCode = code;
    cout << "worker()" << endl;
}

Worker::~Worker()
{
    cout << "~worker" << endl;
}
void Worker::carry()
{
    cout << m_strCode << endl;
    cout << "worker -- carry()" << endl;
}

Farmer.h

#include <string>
using namespace std;
#include "Person.h"

class Farmer:public Person
{
public:
    Farmer(string name = "jack",string color = "blue");
    virtual ~Farmer();
    void sow();
protected:
    string m_strName;
};

Farmer.cpp

#include "Farmer.h"
#include <iostream>
using namespace std;

Farmer::Farmer(string name,string color):Person(color)

{
    m_strName = name;
    cout << "Farmer()" << endl;
}

Farmer::~Farmer()
{
    cout << "~Farmer()" << endl;
}

void Farmer::sow()
{
    cout << m_strName << endl;
    cout << "sow()" << endl;
}

MigrantWorker.h

#include <string>
#include "Farmer.h"
#include "Worker.h"
using namespace std;

class  MigrantWorker:public Worker, public Farmer//此处顺序决定实例化顺序。
{
public:
     MigrantWorker(string name,string code,string color);
     ~ MigrantWorker();
private:
};

MigrantWorker.cpp

#include "MigrantWorker.h"
#include <iostream>
using namespace std;

MigrantWorker::MigrantWorker(string name,string code,string color):  Farmer(name,color), Worker(code,color)
{
    cout <<":MigrantWorker()" << endl;
}

MigrantWorker::~MigrantWorker()
{
    cout << "~MigrantWorker()" << endl;
}

main.cpp

#include <iostream>
#include "MigrantWorker.h"
#include <stdlib.h>

int main()
{
    system("pause");
    return 0;
}

如果不加宏定义会报错,Person类被重定义了。

错误  C2011Person:class”类型重定义

因为我们在Worker和Farmer中都引入了Person.h,这是正常的,但是当MigrantWorker类继承上面两个类,就会引入两遍Person。

公共继承的类需要写上,不是公共继承的也最好写上,因为未来可能会被重定义。

推荐写文件的全称大写,但是其实这个是自定义的,只要能区分开其他文件就可以。

可以正常通过编译说明我们用宏定义成功的解决了菱形问题的重定义报错。

c++虚继承(编码二)

与上小节中其他代码相同。

main.cpp:

int main()
{
    MigrantWorker *p = new MigrantWorker("merry", "200", "yellow");
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

mark

可以看到Person的构造函数执行了两遍。

现在MigrantWorker存在两份Person的成员,下面我们来证明。

  1. 修改Worker.cpp和Farmer.cpp:
Worker::Worker(string code,string color):Person("Worker"+color)
Farmer::Farmer(string name,string color):Person("Farmer"+color)
  1. 通过农民工的指针打印这两份值。

main.cpp

int main()
{
    MigrantWorker *p = new MigrantWorker("merry", "200", "yellow");
    p->Farmer::printColor();
    p->Worker::printColor();
    p = NULL;
    system("pause");
    return 0;
}

mark

可以看到在农民工对象中确实是有两份数据成员color的。

使用虚继承,让农民工只有一份。

Worker.h & Farmer.h中修改

class Worker: virtual public Person//work是虚基类。
{};
class Farmer: virtual public Person
{};

mark

可以看到Person的构造函数只被执行了一次。blue的原因是,既然两个儿子不知道哪个对孙子好,爷爷隔代亲传。

巩固练习

  • 定义Person人类,worker工人类及children儿童类,
  • worker类中定义数据成员m_strName姓名,
  • children类中定义成员m_iAge年龄,
  • worker类及children类均虚公有继承Person类,
  • 定义ChildLabourer童工类,公有继承工人类和儿童类,从而形成菱形继承关系
  • 在main函数中通过new实例化ChildLabourer类的对象,并通过该对象调用Person,Worker及Children类中的成员函数,最后销毁该对象,掌握多重继承,多继承,虚继承的定义方法。
#include <iostream>
#include <stdlib.h>
#include <string>
using namespace std;

/**
 * 定义人类: Person
 */
class Person
{
public:
    Person()
    {
        cout << "Person" << endl;
    }
    ~Person()
    {
        cout << "~Person" << endl;
    }
    void eat()
    {
        cout << "eat" << endl;
    }

};

/**
 * 定义工人类: Worker
 * 虚继承人类
 */
class Worker : virtual public Person
{
public:
    Worker(string name)
    {
        m_strName = name;
        cout << "Worker" << endl;
    }
    ~Worker()
    {
        cout << "~Worker" << endl;
    }
    void work()
    {
        cout << m_strName << endl;
        cout << "work" << endl;
    }
protected:
    string m_strName;
};

/**
 * 定义儿童类:Children
 * 虚继承人类
 */
class Children : virtual public Person
{
public:
    Children(int age)
    {
        m_iAge = age;
        cout << "Children" << endl;
    }   
    ~Children()
    {
        cout << "~Children" << endl;
    }   
    void play()
    {
        cout << m_iAge << endl;
        cout << "play" << endl;
    }
protected:
    int m_iAge;
};

/**
 * 定义童工类:ChildLabourer
 * 公有继承工人类和儿童类
 */
class ChildLabourer: public Children,public Worker
{
public:
    ChildLabourer(string name, int age):Worker(name),Children(age)
    {
        cout << "ChildLabourer" << endl;
    }

    ~ChildLabourer()
    {
        cout << "~ChildLabourer" << endl;
    }   
};

int main(void)
{
    // 用new关键字实例化童工类对象
    ChildLabourer *p = new ChildLabourer("11",12);
    // 调用童工类对象各方法。
    p->eat();
    p->work();
    p->play();
    delete p;
    p = NULL;

    return 0;
}

运行结果:

mark

可以看到多继承中实例化顺序,先声明的哪个先实例化哪个,与初始化列表顺序无关。

已标记关键词 清除标记
相关推荐
<p> <strong><span style="font-size:24px;">课程简介:</span></strong><br /> <span style="font-size:18px;">历经半个多月的时间,</span><span style="font-size:18px;">Debug</span><span style="font-size:18px;">亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。</span><span></span> </p> <p> <span style="font-size:18px;">其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,</span><span style="font-size:18px;">Debug</span><span style="font-size:18px;">还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程!</span><span></span> </p> <p> <br /> </p> <p> <span style="font-size:24px;"><strong>核心技术栈列表</strong></span><span style="font-size:24px;"><strong>:</strong></span> </p> <p> <br /> </p> <p> <span style="font-size:18px;">值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括</span><span style="font-size:18px;">Spring Boot</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Spring MVC</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Mybatis</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Mybatis-Plus</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Shiro(</span><span style="font-size:18px;">身份认证与资源授权跟会话等等</span><span style="font-size:18px;">)</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Spring AOP</span><span style="font-size:18px;">、防止</span><span style="font-size:18px;">XSS</span><span style="font-size:18px;">攻击、防止</span><span style="font-size:18px;">SQL</span><span style="font-size:18px;">注入攻击、过滤器</span><span style="font-size:18px;">Filter</span><span style="font-size:18px;">、验证码</span><span style="font-size:18px;">Kaptcha</span><span style="font-size:18px;">、热部署插件</span><span style="font-size:18px;">Devtools</span><span style="font-size:18px;">、</span><span style="font-size:18px;">POI</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Vue</span><span style="font-size:18px;">、</span><span style="font-size:18px;">LayUI</span><span style="font-size:18px;">、</span><span style="font-size:18px;">ElementUI</span><span style="font-size:18px;">、</span><span style="font-size:18px;">JQuery</span><span style="font-size:18px;">、</span><span style="font-size:18px;">HTML</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Bootstrap</span><span style="font-size:18px;">、</span><span style="font-size:18px;">Freemarker</span><span style="font-size:18px;">、一键打包部署运行工具</span><span style="font-size:18px;">Wagon</span><span style="font-size:18px;">等等,如下图所示:</span><span></span> </p> <img src="https://img-bss.csdn.net/201908070402564453.png" alt="" /> <p> <br /> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:24px;">课程内容与收益</span><span style="font-size:24px;">:</span><span></span> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070403452052.png" alt="" /> </p> <p> <span style="font-size:18px;">总的来说,</span><span style="font-size:18px;">本课程是一门具有很强实践性质的“项目实战”课程,即“</span><span style="font-size:18px;">企业应用员工角色权限管理平台</span><span style="font-size:18px;">”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于</span><span style="font-size:18px;">Shiro</span><span style="font-size:18px;">的资源授权实现员工</span><span style="font-size:18px;">-</span><span style="font-size:18px;">角色</span><span style="font-size:18px;">-</span><span style="font-size:18px;">操作权限、员工</span><span style="font-size:18px;">-</span><span style="font-size:18px;">角色</span><span style="font-size:18px;">-</span><span style="font-size:18px;">数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图:</span> </p> <p> <span></span> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070404285736.png" alt="" /> </p> <p> <br /> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:18px;"><strong>以下为项目整体的运行效果截图:</strong></span> <span></span> </p> <img src="https://img-bss.csdn.net/201908070404538119.png" alt="" /> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405002904.png" alt="" /> </p> <p> <br /> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405078322.png" alt="" /> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405172638.png" alt="" /> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405289855.png" alt="" /> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405404509.png" alt="" /> </p> <p> <br /> </p> <p> <img src="https://img-bss.csdn.net/201908070405523495.png" alt="" /> </p> <p> <br /> </p> <p> <br /> </p> <p> <br /> </p> <p style="text-align:left;"> <span style="font-size:18px;">值得一提的是,在本课程中,</span><span style="font-size:18px;">Debug</span><span style="font-size:18px;">也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:</span><span></span> </p> <img src="https://img-bss.csdn.net/201908070406328884.png" alt="" /> <p> <br /> </p>
<p> <strong><span style="background-color:#FFFFFF;color:#E53333;font-size:24px;">本页面购买不发书!!!仅为视频课购买!!!</span></strong> </p> <p> <strong><span style="color:#E53333;font-size:18px;">请务必到</span></strong><a href="https://edu.csdn.net/bundled/detail/49?utm_source=banner"><strong><span style="color:#E53333;font-size:18px;">https://edu.csdn.net/bundled/detail/49</span></strong></a><strong><span style="color:#E53333;font-size:18px;">下单购买课+书。</span></strong> </p> <p> <span style="font-size:14px;">本页面,仅为观看视频页面,如需一并购买图书,请</span><span style="font-size:14px;">务必到</span><a href="https://edu.csdn.net/bundled/detail/49?utm_source=banner"><span style="font-size:14px;">https://edu.csdn.net/bundled/detail/49</span></a><span style="font-size:14px;">下单购买课程+图书!!!</span> </p> <p> <br /> </p> <p> <span style="font-size:14px;">疯狂Python精讲课程覆盖《疯狂Python讲义》全书的主体内容。</span> </p> <span style="font-size:14px;">内容包括Python基本数据类型、Python列表、元组和字典、流程控制、函数式编程、面向对象编程、文件读写、异常控制、数据库编程、并发编程与网络编程、数据可视化分析、Python爬虫等。</span><br /> <span style="font-size:14px;"> 全套课程从Python基础开始介绍,逐步步入当前就业热点。将会带着大家从Python基础语法开始学习,为每个知识点都提供对应的代码实操、代码练习,逐步过渡到文件IO、数据库编程、并发编程、网络编程、数据分 析和网络爬虫等内容,本课程会从小案例起,至爬虫、数据分析案例终、以Python知识体系作为内在逻辑,以Python案例作为学习方式,最终达到“知行合一”。</span><br />
DirectX修复工具(DirectX Repair)是一款系统级工具软件,简便易用。本程序为绿色版,无需安装,可直接运行。 本程序的主要功能是检测当前系统的DirectX状态,如果发现异常则进行修复。程序主要针对0xc000007b问题设计,可以完美修复该问题。本程序中包含了最新版的DirectX redist(Jun2010),并且全部DX文件都有Microsoft的数字签名,安全放心。 本程序为了应对一般电脑用户的使用,采用了易用的一键式设计,只要点击主界面上的“检测并修复”按钮,程序就会自动完成校验、检测、下载、修复以及注册的全部功能,无需用户的介入,大大降低了使用难度。在常规修复过程中,程序还会自动检测DirectX加速状态,在异常时给予用户相应提示。 本程序适用于多个操作系统,如Windows XP(需先安装.NET 2.0,详情请参阅“致Windows XP用户.txt”文件)、Windows Vista、Windows 7、Windows 8、Windows 8.1、Windows 8.1 Update、Windows 10,同时兼容32位操作系统和64位操作系统。本程序会根据系统的不同,自动调整任务模式,无需用户进行设置。 本程序的V4.0版分为标准版、增强版以及在线修复版。所有版本都支持修复DirectX的功能,而增强版则额外支持修复c++的功能。在线修复版功能与标准版相同,但其所需的数据包需要在修复时自动下载。各个版本之间,主程序完全相同,只是其配套使用的数据包不同。因此,标准版和在线修复版可以通过补全扩展包的形式成为增强版。本程序自V3.5版起,自带扩展功能。只要在主界面的“工具”菜单下打开“选项”对话框,找到“扩展”标签,点击其中的“开始扩展”按钮即可。扩展过程需要Internet连接,扩展成功后新的数据包可自动生效。扩展用时根据网络速度不同而不同,最快仅需数秒,最慢需要数分钟,烦请耐心等待。如扩展失败,可点击“扩展”界面左上角小锁图标切换为加密连接,即可很大程度上避免因防火墙或其他原因导致的连接失败。 本程序自V2.0版起采用全新的底层程序架构,使用了异步多线程编程技术,使得检测、下载、修复单独进行,互不干扰,快速如飞。新程序更改了自我校验方式,因此使用新版本的程序时不会再出现自我校验失败的错误;但并非取消自我校验,因此程序安全性与之前版本相同,并未降低。 程序有更新系统c++功能。由于绝大多数软件运行时需要c++的支持,并且c++的异常也会导致0xc000007b错误,因此程序在检测修复的同时,也会根据需要更新系统中的c++组件。自V3.2版本开始使用了全新的c++扩展包,可以大幅提高工业软件修复成功的概率。修复c++的功能仅限于增强版,标准版及在线修复版在系统c++异常时(非丢失时)会提示用户使用增强版进行修复。除常规修复外,新版程序还支持C++强力修复功能。当常规修复无效时,可以到本程序的选项界面内开启强力修复功能,可大幅提高修复成功率。请注意,请仅在常规修复无效时再使用此功能。 程序有两种窗口样式。正常模式即默认样式,适合绝大多数用户使用。另有一种简约模式,此时窗口将只显示最基本的内容,修复会自动进行,修复完成10秒钟后会自动退出。该窗口样式可以使修复工作变得更加简单快速,同时方便其他软件、游戏将本程序内嵌,即可进行无需人工参与的快速修复。开启简约模式的方法是:打开程序所在目录下的“Settings.ini”文件(如果没有可以自己创建),将其中的“FormStyle”一项的值改为“Simple”并保存即可。 新版程序支持命令行运行模式。在命令行中调用本程序,可以在路径后直接添加命令进行相应的设置。常见的命令有7类,分别是设置语言的命令、设置窗口模式的命令,设置安全级别的命令、开启强力修复的命令、设置c++修复模式的命令、控制Direct加速的命令、显示版权信息的命令。具体命令名称可以通过“/help”或“/?”进行查询。 程序有高级筛选功能,开启该功能后用户可以自主选择要修复的文件,避免了其他不必要的修复工作。同时,也支持通过文件进行辅助筛选,只要在程序目录下建立“Filter.dat”文件,其中的每一行写一个需要修复文件的序号即可。该功能仅针对高级用户使用,并且必须在正常窗口模式下才有效(简约模式时无效)。 本程序有自动记录日志功能,可以记录每一次检测修复结果,方便在出现问题时,及时分析和查找原因,以便找到解决办法。 程序的“选项”对话框中包含了7项高级功能。点击"常规”选项卡可以调整程序的基本运行情况,包括日志记录、安全级别控制、调试模式开启等。只有开启调试模式后才能在C
©️2020 CSDN 皮肤主题: 撸撸猫 设计师:C马雯娟 返回首页