C++远征之封装篇(下)-学习笔记

C++远征之封装篇(下)

c++封装概述

下半篇依然围绕类 & 对象进行展开

将原本学过的简单元素融合成复杂的新知识点。

  • 对象 + 数据成员 = 对象成员(对象作为数据成员)
  • 对象 + 数组 = 对象数组(一个数组中的每个元素都是对象)
  • 深拷贝 & 浅拷贝 (对象之间彼此赋值,彼此拷贝)
  • 对象指针(操作对象) & 对象指针成员
  • this指针
  • const + 对象 -> 常对象
  • const + 函数 -> 常成员函数
  • const + 对象成员 -> 常对象成员

设计了一个精巧的案例,走迷宫。

c++ 对象数组

如何实例化一个对象?实例化对象对于程序是很重要的,只有实例化了对象才能访问成员函数和数据成员。

某些情况下我们往往需要实例化一组对象。比如:我们想实例化出一个50人的班的学生。

mark

一个坐标只能代表一个点,如果我们要想定义一个矩形。定义四个点,四个点的连线形成矩形。

这四个点可以定义为一个数组,每个点就是一个对象。

对象数组: 坐标类

class Coordinate
{
public:
    int m_iX; // x坐标
    int m_iY; // y坐标
}

int main()
{
    Coordinate coord[3];// 栈上实例化对象数组
    coord[1].m_iX = 10;

    Coordinate *p = new Coordinate[3];// 堆上实例化对象数组,调用三次构造函数
    p[0].m_iY =20; // p -> m_iY =20;

    delete []p; // 销毁堆中对象数组,调用三次析构函数
    p =NULL;

    return 0;
}

堆栈

栈区实例化对象数组,会分配相应的内存,系统会自动管理。每一个内存中保存着x,y
堆区分配相应内存,p 与 p[0] 是等价的。

C++对象数组实践代码

要求

2-2-InstanceArray

Coordinate.h

class Coordinate
{
public:
    Coordinate();
    ~Coordinate();
public:
    int m_iX;
    int m_iY;
};

coordinate.cpp

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

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

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

main.cpp

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

int main(void)
{
    Coordinate coor[3];
    coor[0].m_iX = 3;
    coor[0].m_iY = 5;

    Coordinate *p = new Coordinate[3];
    p->m_iX = 7;
    p[0].m_iY = 9;

    p++;
    p->m_iX = 11;
    p[0].m_iY = 13;//此时因为上面p++。p已经指向第二个地址了

    p[1].m_iX = 15;//此时p指向第三个元素

    p++;
    p->m_iY = 17;//此时p指向第三个元素

    for (int i = 0; i < 3; i++)
    {
        cout << coor[i].m_iX << " coor x&y " << coor[i].m_iY << endl;

    }
    for (int j = 0; j < 3; j++)
    {
        //cout << "p_x" << p[j].m_iX << endl;
        //cout << "p_y" << p[j].m_iY << endl;
        cout << "p_x" << p->m_iX << endl;
        cout << "p_y" << p->m_iY << endl;
        p--;
    }

    p++;//因为上面p=-1时才退出了循环。因此释放时已经不是原来那段内存了。

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

mark

遍历就是打印出数组中每个元素的信息。

delete 销毁堆中对象数组,显示了三次的析构函数调用。 系统自动管理的栈中销毁,在我们敲回车时可以看到,也同样调用了三次。

注意让p归位原本位置之后,再执行删除。

c++数组对象实践(二)

之前我们讲使用了new,就要配套的使用delete。

如果new的是一个数组,那么delete时就要加中括号。

可是,为什么数组对应的delete就要加中括号呢?

  • 当实例化一个数组时其实数组中的每一个对象都执行了它的构造函数。
  • 在销毁的时候,我们也希望他们(每个对象)都执行自己的析构函数
  • 如果不加中括号,则只销毁第一个元素。
delete p;//将中括号去掉之后,则只销毁当前指针指向的内存,只执行一次析构函数

mark

可以看到只执行了一次析构函数。

对象成员

前面讲的都是比较简单的对象,数据成员都是基本的数据类型。

  • 对象中包含其他对象(对象成员)

  • 笛卡尔坐标系

mark

以坐标系中的一个线段为例,起点A(2,1),终点B(6,4).

定义一个线段类,每个线段都有一个起点,一个终点。

点坐标的定义:

class Coordinate
{
public:
    Coordinate();
private:
    int m_iX;
    int m_iY;
}

线段的定义:

class Line
{
public:
    Line();
private:
    Coordinate m_coorA;
    Coordinate m_coorB;
}

实例化描述线段

int main(void)
{
    Line *p = new Line();

    delete p;
    p = NULL;
    return 0;
}

结论:

当我们实例化一个line对象时,会先实例化a坐标点,再实例化b坐标点,当这两个对象实例化完成之后再实例化line对象

当销毁时正好与创建相反,先销毁line再销毁b,最后销毁a

比如造汽车->零件->图纸; 拆汽车->图纸->零件

以上情况构造函数都没有参数,坐标类的构造函数其实是需要有参数的。

class Coordinate
{
public:
    Coordinate(int x, int y);
private:
    int m_iX;
    int m_iY;
}

class Line{
public:
    Line(int x1,int y1, int x2,int y2);
private:
    Coordinate m_coorA;
    Coordinate m_coorB;
}

如果直接写成如下会出问题

int main(void)
{
    Line *p = new Line(2,1,6,4);

    delete p;
    p = NULL;
    return 0;
}

因为传进去,并没有赋值给里面的对象成员。
要对代码进一步改造:为line的构造函数配备初始化列表

Line(int x1,int y1, int x2,int y2):m_coorA(x1,y1),m_coorB(x2,y2)
{
    cout << "Line" << endl;
}

c++对象成员实践(一)

要求

2-6-ObjectMember

Coordinate.h :

class Coordinate
{
public:
    Coordinate();
    ~Coordinate();
public:
    int getX();
    void setX(int x);

    int getY();
    void setY(int y);
private:
    int m_iX;
    int m_iY;
};

Coordinate.cpp :

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

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

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

int Coordinate::getX() {
    return m_iX;
}
void Coordinate::setX(int x) {
    m_iX = x;
}
int Coordinate::getY(){
    return m_iY;
}
void Coordinate::setY(int y) {
    m_iY = y;
}

Line.h :

#include "Coordinate.h"

class Line {
public:
    Line();
    ~Line();
    void setCoorA(int x, int y);
    void setCoorB(int x, int y);
    void printInfo();
private:
    Coordinate m_coorA;
    Coordinate m_coorB;
};

Line.cpp :

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

using namespace std;

Line::Line() {
    cout << "Line()" << endl;
}
Line::~Line() {
    cout << "~Line()" << endl;
}
void Line::setCoorA(int x, int y) {
    m_coorA.setX(x);
    m_coorA.setX(y);
}
void Line::setCoorB(int x, int y) {
    m_coorB.setX(x);
    m_coorB.setY(y);
}
void Line::printInfo() {
    cout << "(" <<m_coorA.getX()<< "," <<m_coorA.getY()<< ")" << endl;
    cout << "(" << m_coorB.getX()<< "," << m_coorB.getY()<< ")" << endl;
}

main.cpp

#include <stdlib.h>
#include <iostream>
#include <string>
#include "Line.h"

using namespace std;

int main(void)
{
    Line *p = new Line();

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

mark

上图中我们可以看到类在创建和销毁时对于对象成员的先后处理顺序。

C++对象成员实践(二)[类往对象成员传参]:

作为线段这个类,我们希望在他创建的时候将里面的两个点确定下来。线段类构造函数带参数,并且传给点。

Coordinate(int x, int y);

Line::Line(int x1,int y1,int x2,int y2):m_coorA(x1,y1),m_coorB(x2,y2)
{
    cout << "Line()" << endl;
}

重点在于:

  • Coordinate有两个参数
  • Line的构造函数使用初始化列表

2-6-2-ObjectMemberParameter

Coordinate.h:

class Coordinate
{
public:
    Coordinate(int x, int y);
    ~Coordinate();
public:
    int getX();
    void setX(int x);
    int getY();
    void setY(int y);
private:
    int m_iX;
    int m_iY;
};

Coordinate.cpp :

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

Coordinate::Coordinate(int x,int y)
{
    m_iX = x;
    m_iY = y;
    cout << "Coordinate()"<< m_iX << "," << m_iY << endl;

}
Coordinate::~Coordinate()
{
    cout << "~Coordinate()" << m_iX << "," << m_iY << endl;
}

int Coordinate::getX() {
    return m_iX;
}
void Coordinate::setX(int x) {
    m_iX = x;
}
int Coordinate::getY(){
    return m_iY;
}
void Coordinate::setY(int y) {
    m_iY = y;
}

Line.h:

#include "Coordinate.h"

class Line {
public:
    Line(int x1,int y1,int x2,int y2);
    ~Line();
    void setCoorA(int x, int y);
    void setCoorB(int x, int y);
    void printInfo();
private:
    Coordinate m_coorA;
    Coordinate m_coorB;
};

Line.cpp:

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

using namespace std;

Line::Line(int x1,int y1,int x2,int y2):m_coorA(x1,y1),m_coorB(x2,y2)
{
    cout << "Line()" << endl;
}
Line::~Line() {
    cout << "~Line()" << endl;
}
void Line::setCoorA(int x, int y) {
    m_coorA.setX(x);
    m_coorA.setX(y);
}
void Line::setCoorB(int x, int y) {
    m_coorB.setX(x);
    m_coorB.setY(y);
}
void Line::printInfo() {
    cout << "(" << m_coorA.getX() << "," << m_coorA.getY() << ")" << endl;
    cout << "(" << m_coorB.getX() << "," << m_coorB.getY() << ")" << endl;
}

main.cpp:

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

int main(void)
{
    Line *p = new Line(1,2,3,4);
    p->printInfo();
    delete p;
    p = NULL;
    system("pause");
    return 0;
}

mark

注意:

如果对象A中有对象成员B,对象B没有默认构造函数,那么对象A必须在初始化列表中初始化对象B。

如果对象成员B有默认的构造函数,那么就可以不用在对象A中使用初始化列表初始化B。

单元巩固

定义具有2个对象的Coordinate数组,遍历对象数组,打印对象信息

#include <iostream>
using namespace std;
class Coordinate
{

public:
    Coordinate()
    {
    }
    // 打印坐标的函数
    void printInfo()  
    {
        cout << "("<<m_iX<<","<<m_iY<<")"<<endl;
    }
public:
    int m_iX;
    int m_iY;
};
int main(void)
{
    //定义对象数组
    Coordinate coorArr[2];
    coorArr[0].m_iX = 1;
    coorArr[0].m_iY = 2;
    coorArr[1].m_iX = 3; 
    coorArr[1].m_iY = 4;

    //遍历数组,打印对象信息
    for(int i = 0; i < 2; i++)
    {
        coorArr[i].printInfo();
    }   
    return 0;
}

mark

深拷贝和浅拷贝

讲解拷贝构造函数的时候只讲解了拷贝构造函数的声明方法以及何时会被自动调用。
但没有讲解如何来实现拷贝构造函数,因为对象间的拷贝不简单。

有两种: 深拷贝和浅拷贝

class Array
{
public:
    Array() { m_iCount = 5;}
    Array(const Array& arr)
    { m_iCount = arr.m_iCount;}
private:
    int m_iCount;
};

int main(void)
{
    Array arr1;
    Array arr2 = arr1;
    return 0;
}

实例化arr1时就会调用构造函数Array(), 然后m_iCount = 5。arr2时会调用拷贝构造函数,
Array(const Array& arr) 将arr1作为参数通过arr传入。

增强版

class Array
{
public:
    Array(){
        m_iCount = 5;
        m_pArr = new int[m_iCount];
    }
    Array(const Array& arr){
        m_iCount = arr.m_iCount;
        m_pArr = arr.m_pArr;
    }
private:
    int m_iCount;
    int *m_pArr;
}

int main(void)
{
    Array arr1;
    Array arr2 = arr1;
    return 0;
}
  • 共同的特点,普通版和增强版都只是将数据成员的值进行了简单的拷贝。我们也把这种拷贝模式称之为浅拷贝。对于第一个例子来说使用浅拷贝是没有问题的。

mark

将arr1中的指针拷贝到arr2中,两个指针此时就会指向同一个内存地址。

  • 但是增强版会出现问题就是只简单地把指针拷贝过去的话(对象arr1和arr2的指针会指向同一个地址),一旦修改拷贝对象的数据,被拷贝对象里面的数据也会随之发生改变。(销毁了arr1,指针不存在了, 销毁arr2时会出现空指针回收)

因此,我们想要的是指向两块不同的内存,然后内存中的值(元素)对应相等

mark

class Array
{
public:
    Array(){
        m_iCount = 5;
        m_pArr = new int[m_iCount];
    }
    Array(const Array& arr){
        m_iCount = arr.m_iCount;

        m_pArr = new int[m_iCount]; // 重点实现: 不是直接赋值,而是开辟了自己的内存地址
        for(int i=0;i<m_iCount;i++){
            // 依次对应赋值,保证值相等。
            m_pArr[i] = arr.m_pArr[i];
        }
    }
private:
    int m_iCount;
    int *m_pArr;
}

int main(void)
{
    Array arr1;
    Array arr2 = arr1;
    return 0;
}

深拷贝:新建一个堆,然后就通过循环的方法把堆中的一个一个的数据拷过去。这样就可以避免在修改拷贝对象的数据时,同时改变了被拷贝对象的数据.

浅拷贝代码实践

要求:

要求

说明一下某些场合深拷贝的实用性。

3-2-ShallowCopy

浅拷贝代码:

Array.h

class  Array
{
public:
     Array();
     Array(const Array& arr);
    ~ Array();

    int getCount();
    void setCount(int val);
private:
    int m_iCount;
};

Array.cpp:

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

Array::Array()
{
    cout << "Array()" << endl;
}

Array::Array(const Array& arr) {
    m_iCount = arr.m_iCount;
    cout << "Array(&)" << endl;
}

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

void Array::setCount(int c) {
    m_iCount = c;
}
int Array::getCount() {
    return m_iCount;
}

main.cpp:

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

int main(void)
{
    Array arr1;
    arr1.setCount(5);

    Array arr2 = arr1;

    cout << "arr2,count:" <<arr2.getCount() << endl;
    system("pause");
    return 0;
}

mark

深拷贝代码实践

浅拷贝代码:

Array::Array(const Array& arr) {
    m_pArr = arr.m_pArr; //使用浅拷贝
    m_iCount = arr.m_iCount;
    cout << "Array(&)" << endl;
}

注意因为这样使得两个arr指向同一块内存。在销毁时同一块内存被销毁两次,会产生错误中止。

3-3-DeepCopy

Array.h:

class  Array
{
public:
     Array(int count);
     Array(const Array& arr);
    ~ Array();

    int getCount();
    void setCount(int val);
    void printAddr();
private:
    int m_iCount;
    int *m_pArr;
};

Array.cpp:

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

Array::Array(int count )
{
    m_iCount = count;
    m_pArr = new int[m_iCount];
    cout << "Array()" << endl;
}

Array::Array(const Array& arr) {
    m_pArr = arr.m_pArr;//使用浅拷贝
    m_iCount = arr.m_iCount;
    cout << "Array(&)" << endl;
}

Array::~Array() {
    delete[]m_pArr;
    m_pArr = NULL;
    cout << "~Array()" << endl;
}

void Array::setCount(int c) {
    m_iCount = c;
}
int Array::getCount() {
    return m_iCount;
}
void Array::printAddr() {
    cout << "m_pArr:" << m_pArr << endl;
};

main.cpp

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

int main(void)
{
    Array arr1(5);

    Array arr2(arr1);

    arr1.printAddr();
    arr2.printAddr();
    system("pause");
    return 0;
}

mark

可以看到浅拷贝中,两个指向同一块内存。释放内存时会出现问题。

mark

改造为深拷贝

修改Array.cpp的拷贝函数

Array::Array(const Array &arr) {
    m_iCount = arr.m_iCount;
    m_pArr = new int[m_iCount]; //使用深拷贝
    for (int i =0;i<m_iCount;i++)
    {
        m_pArr[i] = arr.m_pArr[i];
    }
    cout << "Array(&)" << endl;
}

深拷贝:1.申请一段内存。再将源对象的内存中数值拷贝到对应位置一份。

为了看起来更加清晰,在构造函数为arr1的每一个元素赋值

Array::Array(int count)
{
    m_iCount = count;
    m_pArr = new int[m_iCount];
    for (int i=0;i<m_iCount;i++)
    {
        m_pArr[i] = i;
    }
    cout << "Array()" << endl;
}

此时再次运行。不会报错而且两个已经指向不同内存地址。

mark

Array.h中添加void printArr();

在Array.cpp增加打印数组函数:

void Array::printArr() {
    for (int i=0;i<m_iCount;i++)
    {
        cout << m_pArr[i] << endl;
    }
}

此时main.cpp添加:

arr1.printArr();
arr2.printArr();

完整深拷贝代码:

3-3-DeepCopy

Array.h:

class  Array
{
public:
    Array(int count);
    Array(const Array& arr);
    ~Array();

    int getCount();
    void setCount(int val);
    void printAddr();
    void printArr();
private:
    int m_iCount;
    int *m_pArr;
};

Array.cpp:

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

Array::Array(int count)
{
    m_iCount = count;
    m_pArr = new int[m_iCount];
    for (int i = 0; i < m_iCount; i++)
    {
        m_pArr[i] = i;
    }
    cout << "Array()" << endl;
}

Array::Array(const Array& arr) {
    m_iCount = arr.m_iCount;
    m_pArr = new int[m_iCount]; //使用深拷贝
    for (int i = 0; i < m_iCount; i++)
    {
        m_pArr[i] = arr.m_pArr[i];
    }

    cout << "Array(&)" << endl;
}

Array::~Array() {
    delete[]m_pArr;
    m_pArr = NULL;
    cout << "~Array()" << endl;
}

void Array::setCount(int c) {
    m_iCount = c;
}
int Array::getCount() {
    return m_iCount;
}
void Array::printAddr() {
    cout << "m_pArr:" << m_pArr << endl;
};
void Array::printArr() {
    for (int i = 0; i < m_iCount; i++)
    {
        cout << m_pArr[i] << endl;
    }
}

main.cpp:

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

int main(void)
{
    Array arr1(5);

    Array arr2(arr1);

    arr1.printAddr();
    arr2.printAddr();
    arr1.printArr();
    arr2.printArr();
    system("pause");
    return 0;
}

mark

可以看到深拷贝时指向两个不同的地址,其中内容保持一致。

C++对象指针

有一个指针用来指向一个对象。

demo:

class Coordinate{
public:
    int m_iX;
    int m_iY;
}

将我们的坐标类,在堆中实例化:

Coordinate *p = new Coordinate; //执行构造函数

堆中

p指向m_iX,访问方式为p->m_iX;
*p变成一个对象,采用.访问其中元素。

具体示例代码:

int main(void)
{
    Coordinate *p = new Coordinate;
    p -> m_iX = 10;  //(*p).m_iX =10;
    p -> m_iY = 20;  //(*p).m_iY =20;
    delete p;
    p = NULL;
    return 0;
}
  • new 会自动调用对象的构造函数;
  • C语言中的 malloc 则不会调用相关对象的构造函数,只是分配内存。

C++对象指针实践:

要求

4-2-ObjectPointer

Coordinate.h

class Coordinate
{
public:
    Coordinate();
    ~Coordinate();
    int m_iX;
    int m_iY;
};

Coordinate.cpp

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

Coordinate::Coordinate()
{
    cout << "Coordinate()" << endl;
}

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

main.cpp:

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

using namespace std;

int main()
{
    // 使用对象指针指向内存,两种方法

    // 堆中实例化
    Coordinate *p1 = NULL;
    p1 = new Coordinate; //因为有默认构造函数,括号可写可不写
    Coordinate *p2 = new Coordinate(); //方法2

    p1->m_iX = 10;
    p1->m_iY = 20; // 指针方式
    (*p2).m_iX = 30; //*p2使p2变成了一个对象
    (*p2).m_iY = 40;

    cout << (*p1).m_iX + (*p2).m_iX << endl;
    cout << p1->m_iY + p2-> m_iY << endl;

    delete p1;
    p1 = NULL;
    delete p2;
    p2 = NULL;

    system("pause");
    return 0;
}

mark

VS中 先ctrl+kctrl+c可以注释一段代码

    //栈中实例化
    Coordinate p1;
    Coordinate *p2 = &p1;//让p2指向p1的地址,p2可操作p1

    p2->m_iX = 10; // (*p2).m_ix =10;
    p2->m_iY = 20;

    cout << p1.m_iX << "," << (*p2).m_iY << endl;

mark

可以看到p2指向p1的地址,修改p2,p1也会修改。

编码练习

定义一个坐标类,在堆上实例化坐标对象,并给出坐标(3,5),然后打印坐标信息,销毁坐标对象。

#include <iostream>
using namespace std;
class Coordinate
{

public:
    Coordinate(int x, int y)
    {
        // 设置X,Y的坐标
        m_iX = x;
        m_iY = y;
    }
public:
    int m_iX;
    int m_iY;
};

int main(void)
{
    // 在堆上创建对象指针
    Coordinate *p = new Coordinate(3,5);
    // 打印坐标
    cout <<"("<<(*p).m_iX<<","<<(*p).m_iY<<")"<< endl;
    // 销毁对象指针
    delete p;
    p = NULL;
    return 0;
}

mark

C++对象成员指针

  • 对象成员: 一个对象成为另外一个类的数据成员
  • 对象成员指针: 对象的指针成为另外一个类的数据成员

坐标类:

class Coordinate
{

public:
    Coordinate(int x, int y);
public:
    int m_iX;
    int m_iY;
};

线段类:

class Line {
public:
    Line();
    ~Line();
private:
    Coordinate m_coorA; //起点
    Coordinate m_coorB; //终点
};

将线段类代码中的对象成员变成对象成员指针:

class Line {
public:
    Line();
    ~Line();
private:
    Coordinate *m_pCoorA;
    Coordinate *m_pCoorB;
};

初始化时仍可以使用初始化列表进行初始化:

Line::line():m_pCoorA(NULL),m_pCoorB(NULL){
}

也可以在构造函数中使用普通初始化:

Line::line()
{
    m_pCoorB = NULL;
    m_pCoorA = NULL;
}

一般的普通初始化情况:

Line::line()
{
    m_pCoorB = new Coordinate(1,3);
    m_pCoorA = new Coordinate(5,6);
}
Line::~line()
{
    delete m_pCoorA;
    delete m_pCoorB;
}
int main(void)
{
    Line line();
    cout << sizeof(line) <<endl; //8
    return 0;
}

对象成员与对象成员指针的不同:

对于对象成员来说,sizeof是里面所有对象的总和。

指针在32位编译器下占4个基本内存单元,两个指针占8个内存单元。

  • 对象成员指针的定义: 类名 *指针名;
  • 若存在对象成员指针1,2。sizeof(对象),只计算各指针所占内存的总和。

对象成员指针sizeof

创建时line对象中只有两个占4字节的指针。
而实例化出的两个对象在堆中;销毁时,先销毁堆中的,再释放line对象。

对象成员指针实践

要求

4-5-ObjectMemberPointer

Coordinate.h

class Coordinate
{
public:
    Coordinate(int x, int y);
    ~Coordinate();
public:
    int getX();
    int getY();
private:
    int m_iX;
    int m_iY;
};

Coordinate.cpp

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

Coordinate::Coordinate(int x,int y)
{
    m_iX = x;
    m_iY = y;
    cout << "Coordinate()"<<m_iX<<","<<m_iY << endl;

}
Coordinate::~Coordinate()
{
    cout << "~Coordinate()" << m_iX << "," << m_iY << endl;
}

int Coordinate::getX() {
    return m_iX;
}
int Coordinate::getY(){
    return m_iY;
}

Line.h:

#include "Coordinate.h"

class Line {
public:
    Line(int x1,int y1,int x2,int y2);
    ~Line();
    void setCoorA(int x, int y);
    void setCoorB(int x, int y);
    void printInfo();
private:
    Coordinate *m_pCoorA;
    Coordinate *m_pCoorB; //这是一个坐标类的对象指针。它只是一个指针。
};

Line.cpp:

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

Line::Line(int x1,int y1,int x2,int y2){
    m_pCoorA = new Coordinate(x1, y1);
    m_pCoorB = new Coordinate(x2, y2);
    cout << "Line()" << endl;
}
Line::~Line() {
    delete m_pCoorA;
    m_pCoorA = NULL;
    delete m_pCoorB;
    m_pCoorB = NULL;
    cout << "~Line()" << endl;
}
void Line::printInfo() {
    cout << "("<<(*m_pCoorA).getX()<<","<< (*m_pCoorA).getY()<< ")" << endl;
    cout << "(" << m_pCoorB->getX() << "," << m_pCoorB->getY() << ")" << endl;
}

main.cpp:

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

int main(void)
{
    Line *p = new Line(1,2,3,4);
    p->printInfo();

    delete p;
    p = NULL;

    cout << sizeof(p) << endl;
    cout << sizeof(Line) << endl;
    system("pause");
    return 0;
}

运行结果:

mark

  • 可以看出在类包含指针对象时,先实例化指针对象a,b。再去调用父对象的构造函数。
  • 在销毁时也是先按照顺序销毁指针对象a,b;最后销毁父对象。
  • 指针p占用四个内存空间,对象Line中包含两个对象成员指针,也就是包含两个指针,大小为8.

这里我们使用的是32位编译,情况是如上图所示。

mark

可以看到,在64位编译情况下,一个指针占8个字节。

这种情况下对象的销毁与创建顺序一致。

这里我们需要注意对象成员与对象成员指针在创建与销毁时的不同

this指针

  • 对象指针
  • 对象成员指针
  • this指针

例子:

class Array
{
    public:
        Array(int _len){len = _len;}
        int getLen(){return len;}
        void setLen(int _len){len = _len;}
    private:
        int len;
}

参数与数据成员,并不同名。
数据成员与参数在表达同一个意思的时候取相似的名字。

问题: 当参数和数据成员同名会怎么样呢?

class Array
{
    public:
        Array(int len){len = len;} //错
        int getLen(){return len;}
        void setLen(int len){len = len;}//错
    private:
        int len;
}

计算机和人类都无法判断是把参数赋值给成员了,还是成员赋值给参数。无法分辨两个len了。

this指针: 指向对象自身数据的指针

Array arr1;  //this <-> &arr1
Array arr2;  //this <-> &arr2

this

this表达什么地址,取决于当前所在作用域。这样就可以标记出自身的成员,与参数可以分清哪个是哪个了。

class Array
{
    public:
        Array(int len){this->len = len;}//对
        int getLen(){return len;}
        void setLen(int len){this->len = len;}//对
    private:
        int len;
}

对象结构:

对象结构

  • 存在多个对象,成员函数只有代码区内的一份。

又没有传递参数,成员函数如何确定该调用哪个对象的数据成员?

  • 成员函数如何访问到对应对象的数据成员?
class Array
{
    public:
        Array(T *this,int len){this->len = len;}//对
        int getLen(T *this){return this->len;}
        void setLen(T *this,int len){this->len = len;}//对
    private:
        int len;
}

this指针

  • 通过this来实现分辨不同的arr。
  • 编译器自动的为每一个成员函数的参数列表都自动加上了this指针。

this指针在参数列表中的位置(代码实践)

要求:

this代码实践

正常版本1:

4-7-ThisPointerPosition

Array.h

class  Array
{
public:
    Array(int len);
    ~ Array();

    void printAddr();
    void printArr();
    int getLen();
    void setLen(int val);
    void printInfo();
private:
    int m_iLen;
};

Array.cpp:

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

Array::Array(int len)
{
    m_iLen = len;
    cout << "Array()" << endl;
}
Array::~Array() {
    cout << "~Array()" << endl;
}

void Array::setLen(int len) {
    m_iLen = len;
}
int Array::getLen() {
    return m_iLen;
}

void Array::printInfo() {

}

main.cpp:

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

int main(void)
{
    Array arr1(10);

    system("pause");
    return 0;
}

mark

改动: 将所有m_iLen都改为与参数名相同的len,此时已经人眼分辨不出哪个是数据成员,哪个是参数

为了程序更好看(可以分辨出哪个是哪个),我们引入this。

改动Array.h中数据成员:

private:
    int len;

改动过的Array.cpp:

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

Array::Array(int len)
{
    this->len = len;
    cout << "Array()" << endl;
}
Array::~Array() {
    cout << "~Array()" << endl;
}

void Array::setLen(int len) {
    this->len = len;
}
int Array::getLen() {
    return len;
}

void Array::printInfo() {

}

main.cpp添加调用arr1的打印函数。

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

int main(void)
{
    Array arr1(10);
    cout << arr1.getLen() << endl;
    system("pause");
    return 0;
}

mark

this指针实践(二)

4-8-ThisPointerPositionLinkCall

将Array.h中的printInfo()改为:

Array printInfo();

Array.cpp中的printInfo()改为:

Array Array::printInfo() {
    cout << "len:" << len << endl;
    return *this; //this本身是一个指针,而加上*以后变成一个对象。
}

main.cpp:

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

int main(void)
{
    Array arr1(10);
    arr1.printInfo();
    system("pause");
    return 0;
}

mark

这里的printInfo被正常的调用了,但是没有体现出返回this指针的价值。

因为printInfo()是有返回值this指针的,可以进行链式的调用。

arr1.printInfo().setLen(5);
cout << "len_after_set:" << arr1.getLen() << endl;

mark

打印出来的结果可以看出,我们并没有成功的改变掉arr1的值。

因为我们返回的 *this 出来之后又变成了另外一个临时的对象。这是一个临时的对象,并不是arr1。

如果想让它就是arr1,那么引用就可以实现。

Array.h

Array& printInfo();
Array& Array::printInfo() {
    cout << "len:" << len << endl;
    return *this; //this本身是一个指针,而加上*以后变成一个对象。
}

main.cpp:

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

int main(void)
{
    Array arr1(10);
    arr1.printInfo().setLen(5);
    cout << "len_after_set:" << arr1.getLen() << endl;
    system("pause");
    return 0;
}

mark

可以看到因为是引用,arr1都没有被销毁,而是被赋上新值。

我们如果想让链式调用变得更长,只需要给setLen方法,让其也返回this指针的引用。

4-8-ThisPointerPositionLinkCallTwo

Array.h

class  Array
{
public:
     Array(int len);
    ~ Array();

    void printAddr();
    void printArr();
    int getLen();
    Array& setLen(int val);
    Array& printInfo();
private:
    int len;
};

Array.cpp:

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

Array::Array(int len )
{
    this->len = len;
    cout << "Array()" << endl;
}
Array::~Array() {
    cout << "~Array()" << endl;
}

Array& Array::setLen(int len) {
    this->len = len;
    return *this;
}
int Array::getLen() {
    return len;
}

//添加引用之后才是arr1
Array& Array::printInfo() {
    cout << "len:" << len << endl;
    return *this;//this本身是一个指针,而加上*以后变成一个对象。
}

main.cpp:

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

int main(void)
{
    Array arr1(10);
    //因为此时this返回了当前对象。所以可以使用"."
    arr1.printInfo().setLen(5).printInfo();
    system("pause");
    return 0;
}

运行结果:

mark

指针实现版本:

如果我们不是返回一个引用,而是返回一个指针,那又是什么情况?

  • 将返回类型Array &改为 Array *
  • return *this改为return this
  • 将对象的使用符.改为指针操作符->

完整代码如下:
4-8-ThisPointerPositionLinkCallTwoByPointer

Array.h:

class  Array
{
public:
     Array(int len);
    ~ Array();
    void printAddr();
    void printArr();
    int getLen();
    Array* setLen(int val);
    Array* printInfo();
private:
    int len;
};

Array.cpp:

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

Array::Array(int len )
{
    this->len = len;
    cout << "Array()" << endl;
}
Array::~Array() {
    cout << "~Array()" << endl;
}

Array* Array::setLen(int len) {
    this->len = len;
    return this;
}
int Array::getLen() {
    return len;
}

Array* Array::printInfo() {
    cout << "len:" << len << endl;
    return this;//this本身是一个指针.
}

main.cpp:

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

int main(void)
{
    Array arr1(10);
    //因为此时this返回了arr1的指针,所以要使用"->"
    arr1.printInfo()->setLen(5)->printInfo();
    system("pause");
    return 0;
}

mark

最终结果与刚才一致。说明不管是引用还是指针都可以实现改变实际的值。

this指针的本质: 相当于所在对象的地址

在printInfo中打印出this指针的值

Array.cpp:

Array* Array::printInfo() {
    cout << this << endl;
    return this;//this本身是一个指针,而加上*以后变成一个对象。
}

main.cpp:

int main(void)
{
    Array arr1(10);

    arr1.printInfo();
    cout << &arr1 << endl;
    system("pause");
    return 0;
}

运行结果:

mark

this指针无需用户定义,是编译器自动产生的。

常对象成员和常成员函数

const重出江湖

例子:

class Coordinate
{
public:
    Coordinate(int x,int y);
private:
    const int m_iX;
    const int m_iY;
}

//错误做法: 
Coordinate::Coordinate(int x,int y)
{
    m_iY =y;
    m_iX =x;
}

//正确做法:使用初始化列表

Coordinate::Coordinate(int x,int y):m_iX(x),m_iY(y)
{
}

作为一个类的数据成员是可以用const来修饰的,只不过以前讲的都是一些基本数据类型的数据成员。

常对象成员: 对象成员通过const修饰

线段: 一旦起点和终点被确定就不允许修改了。

class Line
{
public:
    Line(int x1,int y1,int x2 ,int y2)
private:
    const Coordinate m_coorA;
    const Coordinate m_coorB;
}

//初始化两个对象使用初始化列表

Line::Line(int x1,int y1,int x2 ,int y2):m_coorA(x1,y1),m_coorB(x2,y2)
{
    cout<< "Line" << endl;
}

//调用:

int main(void)
{
    Line *p = new Line(2,1,6,4);

    delete p;
    p = NULL;
    return 0;
}

const修饰成员函数(常成员函数)

例子:

class Coordinate
{
public:
    Coordinate(int x,int y);
    void changeX() const; // 常成员函数
    void changeX();
private:
    int m_iX;
    int m_iY;
}

//定义常成员函数
void Coordinate::changeX() const
{
    m_iX = 10;//错误
};

//普通函数
void Coordinate::changeX() 
{
    m_iX = 20;
};

思考: 为什么常成员函数中不能改变数据成员的值?

隐藏的参数this指针

实际成员函数有一个隐藏的参数,this指针。

const修饰的指针变成常指针

this指针变成了一个常指针,通过常指针改变数据,显然是不被允许的。

互为重载:

    void changeX() const;
    void changeX(); //互为重载

Q:当直接调用:coordinate.changeX()到底调用的是哪一个呢?

A:调用的是普通的不带const的。

Q: 那么想调用那个带const的如何写?

A:代码如下

int main(void)
{
    // 实例化对象时用const修饰这个对象
    const Coordinate coordinate(3,5);//常对象
    coordinate.changeX(); // 调用的是常成员函数
    return 0;
}

常对象调用的是常成员函数。
普通对象调用的是普通成员函数。

常对象成员与常成员函数代码实践

要求

5-2-ConstantMemberFunction

Coordinate.h :

class Coordinate
{
public:
    Coordinate(int x, int y);
    ~Coordinate();
public:
    int getX() const;//此处声明该成员函数为常成员函数
    void setX(int x); // 相当于setX(Coordinate *this, int x)
    int getY() const;//同上
    void setY(int y);
private:
    int m_iX;
    int m_iY;
};

Coordinate.cpp

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

Coordinate::Coordinate(int x, int y)
{
    m_iX = x;
    m_iY = y;
    cout << "Coordinate()" << m_iX << "," << m_iY << endl;

}
Coordinate::~Coordinate()
{
    cout << "~Coordinate()" << m_iX << "," << m_iY << endl;
}

int Coordinate::getX() const{
    return m_iX;
}
void Coordinate::setX(int x) { 
    m_iX = x;
}
int Coordinate::getY() const{
    return m_iY;
}
void Coordinate::setY(int y) {
    m_iY = y;
}

Line.h:

#include "Coordinate.h"

class Line {
public:
    Line(int x1, int y1, int x2, int y2);
    ~Line();
    void setCoorA(int x, int y);
    void setCoorB(int x, int y);
    void printInfo();
    void printInfo() const;//互为重载

private:
    const Coordinate m_coorA; // Coordinate const m_coorA;
    Coordinate m_coorB;
};

Line.cpp:

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

Line::Line(int x1, int y1, int x2, int y2) :m_coorA(x1, y1), m_coorB(x2, y2) {
    cout << "Line()" << endl;
}
Line::~Line() {
    cout << "~Line()" << endl;
}
void Line::setCoorA(int x, int y) {
    //m_coorA.setX(x); // 出现问题: 此时相当于在setX中传递了一个this指针
    //m_coorA.setX(y);
}
void Line::setCoorB(int x, int y) {
    m_coorB.setX(x);
    m_coorB.setY(y);
}
void Line::printInfo() {
    cout << "printInfo()" << endl;
    cout << "(" << m_coorA.getX() << "," << m_coorA.getY() << ")" << endl;
    cout << "(" << m_coorB.getX() << "," << m_coorB.getY() << ")" << endl;

}
void Line::printInfo() const{
    cout << "printInfo() const" << endl;
    cout << "(" << m_coorA.getX() << "," << m_coorA.getY() << ")" << endl;
    cout << "(" << m_coorB.getX() << "," << m_coorB.getY() << ")" << endl;
}

main.cpp

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

int main(void)
{
    Line line(1, 2, 3, 4);
    line.printInfo();//调用的是普通的

    const Line line2(1, 2, 3, 4);
    line2.printInfo();//调用的是常成员函数

    system("pause");
    return 0;
}

mark

void setX(int x); // 相当于setX(Coordinate *this, int x)

我们在定义的时候,Coordinate *this要求的是一个既有写权限又有读权限的指针。

而我们在调用的时候,我们传入的m_coorA是一个只有读权限的对象。

如果我们想让函数能用,加const就可以了。setX是不能加的,不然就不能修改x,y的值了
但是getX 和 getY是可以加的。

    int getX() const;//此处声明该成员函数为常成员函数
    int getY() const;// const应写在函数声明的后面

然后将const也要同步到定义上去。此处略去自行完成。将setX和setY注释掉。

int getX(const Coordinate *this) const;

这就要求我们传入的是常对象。

  • 常对象只能调用常成员函数。
  • 普通对象可以调用全部成员函数(非常成员函数)。

思考:为什么需要const成员函数?

我们定义的类的成员函数中,常常有一些成员函数不改变类的数据成员,也就是说,这些函数是”只读”函数,而有一些函数要修改类数据成员的值。如果把不改变数据成员的函数都加上const关键字进行标识,显然,可提高程序的可读性。其实,它还能提高程序的可靠性,已定义成const的成员函数,一旦企图修改数据成员的值,则编译器按错误处理。

常指针与常引用

对象指针与对象引用

class Coordinate
{
public:
    Coordinate(int x, int y);
public:
    int getX();
    int getY();
    void printInfo() const; // 常成员函数
private:
    int m_iX;
    int m_iY;
};

定义:

int Coordinate::getX(){
    return m_iX;
}

int Coordinate::getY() const{
    return m_iY;
}

void Coordinate::printInfo() const
{
    cout << "(" <<m_iX <<","<<m_iY <<")"<<endl;
}

对象的引用 & 对象的指针

int main(void)
{
    Coordinate coor1(3,5);
    Coordinate &coor2 = coor1; //对象的引用,起别名
    Coordinate *pCoor = &coor1;//对象的指针,注意取地址符。
    coor1.printInfo();
    coor2.printInfo(); //也将打印出coor1的坐标(3,5)
    pCoor -> printInfo();
    return 0;
}

对象的常指针与对象的常引用

int main(void)
{
    Coordinate coor1(3,5);
    const Coordinate &coor2 = coor1; //常:对象的引用
    const Coordinate *pCoor = &coor1; //常:对象的指针,注意取地址符。
    coor1.printInfo(); //普通对象正常使用

    coor2.getX(); //错误,常引用(只具有读权限的this指针)
    // 定义getX的时候没有加const,因此它内部隐藏传入的this是要求一个读写权限都有的。

    // 只能调用常成员函数
    coor2.printInfo();

    pCoor -> getY(); //错误。常指针(只有只读权限)。
    //getY的隐藏参数要求传入的是读写权限的this

    // 只能调用常成员函数
    pCoor -> printInfo();

    return 0;
}

更复杂的例子:

int main()
{
    Coordinate coor1(3,5);
    Coordinate coor2(7,9);

    //定义一个对象指针
    Coordinate *const pCoor = &coor1; 
    // 注意,const在*的后面。
    // pCoor一旦指向一个对象就不能指向其他的对象

    //指针不可以指向其他对象,但是指针本身指向的对象内容可变。
    //这是一个具有读写权限的指针。只限于当前指向的对象。
    pCoor ->getY(); //正确

    //pCoor已经被const修饰了,不允许修改
    pCoor = &coor2;

    //printInfo是一个常成员函数(要求读权限的指针),而pCoor具有读写权限,大权限可以调用小权限的。
    pCoor -> printInfo();

    return 0;
}

常指针容易混淆的:

  • const *p -> *p不可以再赋值
  • *const p -> p不可以再赋值
  • const * const p -> *pp都不可以再赋值

不能把小权限的指向大权限的,但是可以把大权限的指向小权限的。

  • 常对象只能调用常成员函数,不能调用普通成员函数
  • 普通对象能够调用常成员函数,也能够调用普通成员函数(权限大调用权限小)
  • 常指针和常引用都只能调用对象的常成员函数。(小权限调小权限)
  • 一个对象可以有多个对象常引用 (小名自身不可以改,但是可以有多个别名)

单元巩固

定义一个坐标类,在栈上实例化坐标类常对象,并给出坐标(3,5),然后定义常引用、常指针,最后使用对象、引用、指针分别通过调用信息打印函数打印坐标信息。

#include <iostream>
using namespace std;
class Coordinate
{

public:
    Coordinate(int x, int y)
    {
        // 设置X,Y的坐标
        m_iX = x;
        m_iY = y;
    }
    // 实现常成员函数
    void printInfo() const
    {
        cout << "("<<m_iX<<","<<m_iY<<")"<<endl;
    }
public:
    int m_iX;
    int m_iY;
};


int main(void)
{
    const Coordinate coor(3, 5);

    // 创建常指针p
    const Coordinate *p = &coor;
    // 创建常引用c
    const Coordinate &c = coor;

    coor.printInfo();
    p->printInfo();
    c.printInfo();  

    return 0;
}

mark

迷宫程序(走出迷宫)

走出规则(算法):

  • 左手规则 & 右手规则
  • 原则:保证手始终触墙(黑暗中在家扶墙走)
  • 结果:走出迷宫

情况1(建议设计成这样):有入有出。

mark

情况2:出入口是一个)

出入为一个

架构描述

涉及两个类: 迷宫类(MazeMap)& 走迷宫的人(Person)

二维数组:

1代表墙,0代表路,自己决定。

1墙0路

迷宫类(MazeMap)

数据成员:

- 墙壁字符
- 通路字符
- 迷宫数组

成员函数:

- 构造函数
- 数据封装函数
- 迷宫回执函数
- 迷宫边界检查函数

人类(MazePerson)

数据成员:

- 人的字符
- 人的朝向
- 人当前位置(设置在入口)
- 人前一个位置 (人走动,前位置抹掉,后一个位置绘制)
- 人的速度

成员函数:

- 构造函数
- 数据封装函数
- 向不同方向前进的函数(上下左右)
- 转弯函数
- 开始函数

控制台动画控制:

/*
 * 函数名称:gotoxy
 * 函数功能:确定控制台中字符的输出位置
 * 函数列表:
 *      x:横坐标
 *      y:纵坐标
 */

void MazePerson::gotoxy(int x, int y)   
{   
    COORD cd;    
    cd.X   =   x; 
    cd.Y   =   y;
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);  
    SetConsoleCursorPosition(handle,cd); // 把光标定位到相应位置上    
};

首先需要定义一个迷宫,迷宫是一个二维数组,WaLL是墙,Road是路。

绘制迷宫cout就可以了。有了迷宫的二维数组,先实例化一个迷宫对象,通过setMazeMap函数将
二维数组设置进去,SetMazeWall告诉计算机墙用什么表示。设置好之后绘制迷宫。

走迷宫的人,设置人的位置位于入口。设置人的速度,设置人的字符形状。

人开始运动。

注意事项:

  • 枚举类型:方向(上下左右)
  • 常量定义:宏定义 & const

成就感源于克服困难

迷宫代码实现

未完待续, 之后补充。

已标记关键词 清除标记
相关推荐
<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马雯娟 返回首页