C++学习(day5)

一. 静态成员变量和函数(static)

1. 静态成员变量

定义:

  • 由static修饰的成员变量,称为静态成员变量

格式:

  • static 成员变量 如: static int a;

权限:

  • 静态成员一般权限为public

注意:

  1. 静态成员不占用类的大小,独立于类体而存在,编译时在静态区分配空间

  2. 静态成员独立于类对象而存在,即使没有实例化对象,也能使用该静态成员,类名加作用域限定符

  3. 静态成员由所有类对象共享,一个类对象对其进行改变,所有类对象的该属性都改变

  4. 静态成员功能上类似于全局变量,但是,静态成员比全局变量更能体现类的封装性。

  5. 静态成员即使在内存上独立,但是也还是属于类中成员,其他类对象不能直接使用该属性

  6. 静态成员变量需要在全局处进行声明,声明时可以给定初始值,也可以不给,不给定初始值时,默认为0

2. 静态成员函数

定义:

  • 静态成员函数不隶属于某个类对象而独立存在,功能上类似于全局函数

格式:

  • static 函数名 如: static void test();

调用方式:

  • 类对象调用
  • 类名调用

注意事项:

  1. 静态成员函数中,只能使用静态成员变量,不能使用非静态成员变量

  2. 静态成员函数中,没有this指针,因为静态成员函数不依附于类对象,而this指针指代的就是类对象

  3. 静态成员函数与同名的非静态成员函数不构成重载关系,因为作用域不同

代码示例:

#include <iostream>

using namespace std;

class Stu
{
private:
    string name; // 字节长度 32
    int age;     // 字节长度 4

public:
    static double flag; // 共享数据,声明一个静态局部变量
    int num = 999;

    Stu() {}                                  // 无参构造
    Stu(string n, int a) : name(n), age(a) {} // 有参构造

    void show()
    {
        cout << "name = " << name << "  age = " << age << "  flag = " << flag << endl;
    }

    // static void show()           //静态成员函数和同名的非静态成员函数不构成重载关系,因为作用域不同
    static void display() // 静态成员函数
    {
        // cout<<"name = "<<name<<"  age = "<<age<<endl;    //在静态成员函数中,不能使用非静态成员变量
        cout << "flag = " << flag << endl; // 可以使用静态成员变量
    }
};

double Stu::flag = 520; // 在类外进行定义,并给定初始值,如果不给初始值,默认为0

int main()
{

    // cout<<"Stu :: num = "<<Stu::num<<endl;           //非静态成员变量,只能通过类对象调用
    cout << "Stu :: flag = " << Stu::flag << endl; // 静态成员变量,可以直接使用类名调用,无需实例化对象
    Stu::flag = 666;
    cout << "&Stu::flag = " << &Stu::flag << endl;

    Stu s1("zhangpp", 18);
    s1.show(); // 520

    Stu s2("lisi", 20);
    s2.show(); // 520

    cout << "&s1.flag = " << &s1.flag << endl;
    cout << "&s2.flag = " << &s2.flag << endl;

    s2.flag = 1314; // 其中一个类对象对其进行修改
    s2.show();      // 1314
    s1.show();      // 1314

    cout << sizeof(Stu) << endl; // 40,如果占类的内存,则应该是48

    // 通过类对象调用静态成员函数
    s1.display();
    // 通过类型进行调用
    // Stu::show();          //非静态成员函数只能通过类对象调用
    Stu::display(); // 静态成员函数可以通过类对象直接调用

    return 0;
}

二. 类的继承(inhert)

面对对象的三大特性

  • 封装
  • 继承
  • 多态

1. 继承

所谓继承,就是基于一个已有的类去定义一个新类的过程

2. 继承的作用

  1. 能够大大提高代码的复用性,父类有的属性和行为,在子类中无需再次定义,直接使用即可

  2. 继承是多态的必要条件,没有继承,就没有多态

3. 一个类B继承类A

  • 此时我们称A类为父类、基类

  • B类称为子类、派生类

4. 继承格式

class B:继承方式 A
{
    //子类拓展成员
};

继承方式:

父类:public|protected|private|不能访问public|protected|private|不能访问public|protected|private|不能访问
继承方式publicprotectedprivate
子类:public|protected|不能访问|不能访问protected|protected|不能访问|不能访问private|private|不能访问|不能访问

注意:

  1. 继承方式其实就是子类继承父类中访问权限的最高权限
  2. 默认继承方式是私有继承,常用的继承方式是public
#include <iostream>

using namespace std;

class Person
{
public:
    string name; // 32

protected:
    int pwd; // 银行卡密码   4

private:
    int money; // 私房钱   4

public:
    Person() { cout << "Person::无参构造" << endl; }
    Person(string n, int p, int m) : name(n), pwd(p), money(m) { cout << "Person::有参构造" << endl; }

    void show()
    {
        cout << "Person::name = " << name << endl;   // 自己的公有成员,类内可以被访问
        cout << "Person::pwd = " << pwd << endl;     // 自己的受保护成员,类内可以被访问
        cout << "Person::money = " << money << endl; // 自己的私有成员,类内可以被访问
    }
};

// 定义一个学生类,继承自Person类
class Stu : public Person
// class Stu : Person            //如果不加继承方式,默认为私有继承
{
public:
    int num; // 学号   4

private:
    double score; // 成绩  8

public:
    Stu() {}
    Stu(int n, double s) : num(n), score(s) {}
    ~Stu() {}

    void display()
    {
        cout << "Stu::name = " << name << endl; // 继承自父类的公共权限下成员,子类中也是公共的,类内能被访问
        cout << "Stu::pwd = " << pwd << endl;   // 继承自父类的受保护权限下成员,子类中也是受保护的,类内能被访问
        // cout<<"Stu::money = "<<money<<endl;   //继承自父类的私有权限下成员,子类中是不可访问的
        cout << "Stu::num = " << num << endl;     // 自己类中的公有成员,类内能被访问
        cout << "Stu::score = " << score << endl; // 自己类中的私有成员,类内能被访问
    }
};

int main()
{
    cout << sizeof(Stu) << endl; // 子类继承的父类中所有的成员,包括私有成员

    Stu s;                                    // 调用子类的无参构造
    cout << "Stu::name = " << s.name << endl; // 继承自父类的公共权限下成员,子类中也是公共的,类外能被访问
    // cout<<"Stu::pwd = "<<s.pwd<<endl;         //继承自父类的受保护权限下成员,子类中也是受保护的,类外不能被访问
    // cout<<"Stu::money = "<<s.money<<endl;   //继承自父类的私有权限下成员,子类中是不可访问的,类外也不能被访问
    cout << "Stu::num = " << s.num << endl; // 自己类中的公有成员,类内能被访问,类外也能被访问
    // cout<<"Stu::score = "<<s.score<<endl;        //自己类中的私有成员,类内能被访问,类外不能被访问

    return 0;
}

5. 子类会继承父类的所有成员

  1. 子类会继承父类中的所有成员,包括私有成员

  2. 想要对子类从父类中继承下来那部分成员的空间申请以及初始化,必须在子类的构造函数的初始化列表中显性调用父类的有参构造来完成。

  3. 如果没有在子类的初始化列表中显性调用父类的有参构造,则系统会自动调用父类的无参构造,来完成对继承下来的成员的空间申请以及初始化

  4. 如果父类中只有有参构造,没有显性定义无参构造,则第3条会报错

  5. 在实例化子类的过程中,虽然会调用父类的构造函数,但是,仅仅是使用父类构造函数完成对子类中从父类继承下来成员的构造,并没有实例化父类对象,所有,最终实例化的类对象只有子类一个

  6. 所谓继承关系(is a),其本质是一个包含关系(has a)

image-20230423165942124

#include <iostream>

using namespace std;

class Person
{
public:
    string name; // 32

protected:
    int pwd; // 银行卡密码   4

private:
    int money; // 私房钱   4

public:
    Person() { cout << "Person::无参构造" << endl; }
    Person(string n, int p, int m) : name(n), pwd(p), money(m) { cout << "Person::有参构造" << endl; }

    void show()
    {
        cout << "Person::name = " << name << endl;   // 自己的公有成员,类内可以被访问
        cout << "Person::pwd = " << pwd << endl;     // 自己的受保护成员,类内可以被访问
        cout << "Person::money = " << money << endl; // 自己的私有成员,类内可以被访问
    }
};

// 定义一个学生类,继承自Person类
class Stu : public Person
// class Stu : Person            //如果不加继承方式,默认为私有继承
{
public:
    int num; // 学号   4

private:
    double score; // 成绩  8

public:
    Stu() : Person("张三", 123456, 10000000) // 显性调用了父类的无参构造
    {
        cout << "Stu::无参构造" << endl;
    }

    Stu(int n, double s, string n1, int p1, int m1) : Person(n1, p1, m1), num(n), score(s)
    {
        cout << "Stu::有参构造" << endl;
    }

    ~Stu() {}

    void display()
    {
        cout << "Stu::name = " << name << endl; // 继承自父类的公共权限下成员,子类中也是公共的,类内能被访问
        cout << "Stu::pwd = " << pwd << endl;   // 继承自父类的受保护权限下成员,子类中也是受保护的,类内能被访问
        // cout<<"Stu::money = "<<money<<endl;   //继承自父类的私有权限下成员,子类中是不可访问的
        cout << "Stu::num = " << num << endl;     // 自己类中的公有成员,类内能被访问
        cout << "Stu::score = " << score << endl; // 自己类中的私有成员,类内能被访问
    }
};

int main()
{
    //    Stu s1;

    //    s1.display();

    Stu s2(1001, 99, "lisi", 8888888, 666666);
    // s2.display();

    return 0;
}

6. 当父子类中出现同名的成员时

  1. 不会报错,此时子类中会重复出现两个该成员

  2. 如果直接在子类中,或者使用子类对象进行直接使用,那么使用的是子类自己定义的该成员

  3. 如果非要使用父类继承下来的该成员,则需要在成员名前加父类名和作用域限定符

7. 继承中构造函数和析构函数调用顺序

构造时:先构造父类,再构造子类

析构时:先析构子类,再析构父类

8. 子类继承父类的步骤

  1. 全盘吸收父类中中的所有成员

  2. 改造父类中的成员,可以使用继承方式对父类的相关权限改造

    也可以使用using关键字改变父类的相关权限

    using 父类::成员名;

  3. 拓展新成员

三. 继承中特殊的成员函数

1. 构造函数

构造函数不会被继承,父子类中有自己的构造函数

  1. 需要在子类的构造函数的初始化列表中,显性调用父类的构造函数,来完成对子类中从父类继承下来成员的空间申请以及初始化工作

  2. 如果没有显性调用父类的有参构造,则系统会自动调用父类的无参构造,,来完成对子类中从父类继承下来成员的空间申请以及初始化工作

  3. 先构造父类,再构造子类

2. 析构函数

析构函数不会被重载,父子类中有自己的析构函数

  1. 无需在子类的析构函数中,显性调用父类的析构函数,系统会在析构子类的同时,自动调用父类的析构函数,完成对子类从父类中继承下来成员的内存回收工作

  2. 先析构子类,在析构父类

  3. 对于指针成员,父类中的指针由父类的析构函数完成内存释放,子类中的指针有子类的析构函数完成内存释放。

3. 拷贝构造函数

拷贝构造函数不会被继承,父子类中有自己的拷贝构造函数

  1. 需要在拷贝构造函数的初始化列表中,显性调用父类的拷贝构造函数,来完成对子类中从父类继承下来成员的空间申请以及初始化工作

  2. 如果没有在子类的拷贝构造函数中显性调用父类的拷贝构造函数,则系统会自动调用父类的无参构造

  3. 拷贝构造函数涉及深浅拷贝问题

4. 拷贝赋值函数

拷贝赋值函数不会被继承,父子类中有自己的拷贝赋值函数

  1. 需要在拷贝赋值函数的函数体内,显性调用父类的拷贝赋值函数,来完成对子类从父类中继承成员的赋值

  2. 如果没有在拷贝赋值函数函数体内,显性调用父类的拷贝赋值函数,则对父类继承下来的成员,保持原值不变

  3. 拷贝赋值函数涉及深浅拷贝问题

#include <iostream>

using namespace std;

class Person
{
public:
    string name; // 32

protected:
    int pwd; // 银行卡密码   4

private:
    int money; // 私房钱   4

public:
    Person() { cout << "Person::无参构造" << endl; }
    Person(string n, int p, int m) : name(n), pwd(p), money(m) { cout << "Person::有参构造" << endl; }
    ~Person() { cout << "Person::析构函数" << endl; }

    void show()
    {
        cout << "Person::name = " << name << endl;   // 自己的公有成员,类内可以被访问
        cout << "Person::pwd = " << pwd << endl;     // 自己的受保护成员,类内可以被访问
        cout << "Person::money = " << money << endl; // 自己的私有成员,类内可以被访问
    }

    // 定义person拷贝构造函数
    Person(const Person &other) : name(other.name), pwd(other.pwd), money(other.money)
    {
        cout << "Person::拷贝构造函数" << endl;
    }

    // 定义person的拷贝赋值函数
    Person &operator=(const Person &other)
    {
        if (this != &other)
        {
            this->name = other.name;
            this->pwd = other.pwd;
            this->money = other.money;
        }
        cout << "Person::拷贝赋值函数" << endl;

        return *this;
    }
};

// 定义一个学生类,继承自Person类
class Stu : public Person
// class Stu : Person            //如果不加继承方式,默认为私有继承
{
public:
    int num; // 学号   4
    // using Person::pwd;              //改造父类中继承下来成员的权限
    // using Person::money;           //对父类中继承下来的私有权限不能修改

private:
    double score; // 成绩  8

    // using Person::name;

public:
    Stu() : Person("张三", 123456, 10000000) // 显性调用了父类的无参构造
    {
        cout << "Stu::无参构造" << endl;
    }

    Stu(int n, double s, string n1, int p1, int m1) : Person(n1, p1, m1), num(n), score(s)
    {
        cout << "Stu::有参构造" << endl;
    }

    ~Stu() { cout << "Stu::析构函数" << endl; } // 析构函数

    // 定义子类的拷贝构造函数
    // 父类的引用可以指向子类的对象
    Stu(const Stu &other) : Person(other), num(other.num), score(other.score)
    {
        cout << "Stu::拷贝构造函数" << endl;
    }

    // 定义子类的拷贝赋值函数
    Stu &operator=(const Stu &other)
    {
        if (this != &other)
        {
            this->num = other.num;
            this->score = other.score;

            // 显性调用父类的拷贝赋值函数
            //*(Person::this) = other;   //Person::operator=(other);
            Person::operator=(other);
        }
        cout << "Stu::拷贝赋值函数" << endl;

        return *this;
    }

    void show()
    {
        this->Person::show(); // 在子类中调用父类继承下来的同名函数

        cout << "Stu::name = " << Person::name << endl; // 继承自父类的公共权限下成员,子类中也是公共的,类内能被访问
        cout << "Stu::pwd = " << pwd << endl;           // 继承自父类的受保护权限下成员,子类中也是受保护的,类内能被访问
        // cout<<"Stu::money = "<<money<<endl;   //继承自父类的私有权限下成员,子类中是不可访问的
        cout << "Stu::num = " << num << endl;     // 自己类中的公有成员,类内能被访问
        cout << "Stu::score = " << score << endl; // 自己类中的私有成员,类内能被访问
    }
};

int main()
{

    //    s1.display();

    Stu s2(1001, 99, "lisi", 8888888, 666666);
    // s2.Person::show();                   //此时调用子类从父类中继承下来的show函数
    // s2.show();                            //此时调用的是子类自己拓展的show函数

    // cout<<"s2.pwd = "<<s2.pwd<<endl;
    Stu s1(s2); // 调用拷贝构造

    Stu s3; // 无参构造

    s3 = s2; // 调用拷贝赋值函数

    s3.show();

    return 0;
}

四. 多重继承

一个子类,可以由多个父类共同派生出来,该子类拥有所有父类的特征。

定义格式:

class 子类: 继承方式1 父类1, 继承方式2 父类2,。。。,继承方式n 父类n
{
    //子类拓展成员
}

多重继承会产生的问题:

  1. 多个父类中出现同名的成员,在子类中会产生歧义,此时访问时需要加上父类名和作用域限定符

  2. 需要在子类的构造函数初始化列表中,显性调用多个父类的构造函数,完成对数据的初始化

  3. 父类的构造函数调用顺序,跟继承顺序有关,跟调用顺序无关

#include <iostream>

using namespace std;

class A
{
protected:
    int value_a;
    int num;

public:
    A() { cout << "A::无参构造" << endl; }
    A(int a, int n) : value_a(a), num(n) { cout << "A::有参构造" << endl; }
};

class B
{
protected:
    int value_b;
    int num;

public:
    B() { cout << "B::无参构造" << endl; }
    B(int b, int n) : value_b(b), num(n) { cout << "B::有参构造" << endl; }
};

// 定义一个C类,公共继承者A类和B类
// 调用父类的构造函数顺序,跟继承顺序有关
class C : public B, public A
{
private:
    string name;

public:
    C() {}
    // 调用父类的构造函数的顺序,跟是否先调用构造函数无关
    C(int n1, int v1, int n2, int v2, string n) : B(n2, v2), A(n1, v1), name(n)
    {
        cout << "C::有参构造" << endl;
    }

    void show()
    {
        cout << "value_a = " << value_a << endl;
        cout << "value_b = " << value_b << endl;
        cout << "name = " << name << endl;

        cout << "num = " << A::num << endl; // 此时输出的是从A类继承下来的num
        cout << "num = " << B::num << endl; // 此时输出的是从B类继承下来的num
    }
};

int main()
{
    cout << sizeof(C) << endl;

    C c(1, 2, 3, 4, "zahngpp");
    return 0;
}

五. 虚继承

1. 菱形继承(钻石继承)

2020030312021513.png

该继承就成为菱形继承

对于公共基类(A),的成员,会根据不同路径传递给汇聚子类,那么,此时汇聚子类中,就会保留多份公共基类传下来的数据 访问起来会有歧义,而且也没必要保留那么多数据,造成汇聚子类内存爆炸

2. 虚继承格式

  1. 虚继承是解决菱形继承问题,保证汇聚子类中只保留一份公共基类传下来的数据

  2. 在生成中间子类时,在继承方式前加关键字 virtual即可

  3. 在构造子类时,原本只需调用直接父类的构造函数即可完成对继承下来成员的初始化,但是,在虚继承情况下,由于公共基类的数据只串一份数据给汇聚子类,不确定通过哪一条路径传递,索性直接由公共基类直接对其进行构造

  4. 要求在汇聚子类的初始化列表中,显性调用公共基类的构造函数,完成对从公共基类继承下来成员的空间开辟和初始化工作

#include <iostream>

using namespace std;

class A //------>公共基类
{
protected:
    int aa;

public:
    A() { cout << "A::无参构造" << endl; }
    A(int a) : aa(a) { cout << "A::有参构造" << endl; }
};

// 由公共基类生成中间子类
class B : virtual public A
{
protected:
    int bb;

public:
    B() { cout << "B::无参构造" << endl; }
    B(int a, int b) : A(a), bb(b) { cout << "B::有参构造" << endl; }
};

class C : virtual public A
{
protected:
    int cc;

public:
    C() { cout << "C::无参构造" << endl; }
    C(int a, int c) : A(a), cc(c) { cout << "C::有参构造" << endl; }
};

// 由两个中间子类生成汇聚子类
class D : public B, public C
{
private:
    int dd;

public:
    D() { cout << "D::无参构造" << endl; }
    D(int a1, int a2, int b, int c, int d) : A(a1), B(a1, b), C(a2, c), dd(d) { cout << "D::无参构造" << endl; }

    void show()
    {
        cout << "bb = " << bb << endl;
        cout << "cc = " << cc << endl;
        cout << "dd = " << dd << endl;
        // cout<<"aa = "<<B::aa<<endl;        //从A->B->D传下来的aa变量
        // cout<<"aa = "<<C::aa<<endl;        //从A->C->D传下来的aa变量
        cout << "aa = " << aa << endl; // 由于是虚继承,汇聚子类中只保留一份公共基类的数据
    }
};

int main()
{
    cout << sizeof(D) << endl; // 20

    D t(1, 2, 3, 4, 5); // 调用有参构造

    t.show();

    return 0;
}

D : public B, public C
{
private:
int dd;

public:
D() { cout << “D::无参构造” << endl; }
D(int a1, int a2, int b, int c, int d) : A(a1), B(a1, b), C(a2, c), dd(d) { cout << “D::无参构造” << endl; }

void show()
{
    cout << "bb = " << bb << endl;
    cout << "cc = " << cc << endl;
    cout << "dd = " << dd << endl;
    // cout<<"aa = "<<B::aa<<endl;        //从A->B->D传下来的aa变量
    // cout<<"aa = "<<C::aa<<endl;        //从A->C->D传下来的aa变量
    cout << "aa = " << aa << endl; // 由于是虚继承,汇聚子类中只保留一份公共基类的数据
}

};

int main()
{
cout << sizeof(D) << endl; // 20

D t(1, 2, 3, 4, 5); // 调用有参构造

t.show();

return 0;

}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值