Skip to content

不同类型的成员

普通成员

普通成员就是最简单的成员,没有其他关键字进行修饰。形如

cpp
class Test{
public:
    void setAge(int a) { age = a; }     // 普通成员函数
    int getAge(int a) { return age; }   // 普通成员函数

private:
    int age;    // 普通成员变量
};

对于普通成员,我们按照一般方式进行使用即可,但是如果成员被const、static、noexcept等关键字进行修饰了,我们就要注意它们与普通成员之间的区别。

静态成员

Tips

静态变量,是在编译阶段就分配空间,对象还没有创建时,就已经分配空间。

在类定义中,它的成员(包括成员变量和成员函数),这些成员可以用关键字static声明为静态的,称为静态成员。

不管这个类创建了多少个对象,静态成员只有一个拷贝,这个拷贝被所有属于这个类的对象共享。

静态成员变量

在一个类中,若将一个成员变量声明为static,这种成员称为静态成员变量。与一般的数据成员不同,无论建立了多少个对象,都只有一个静态数据的拷贝。静态成员变量,属于某个类,所有对象共享

Tips


代码演示
cpp
#include <iostream>
using namespace std;

class Person {
public:
    //类的静态成员属性
    static int sNum;

private:
    static int sOther;
};

//类外初始化,初始化时不加static
int Person::sNum = 0;
int Person::sOther = 0;

int main(int argc, char *argv[]) {
    //1. 通过类名直接访问
    Person::sNum = 100;
    cout << "Person::sNum:" << Person::sNum << endl;

    //2. 通过对象访问
    Person p1, p2;
    p1.sNum = 200;
    cout << "p1.sNum:" << p1.sNum << endl;
    cout << "p2.sNum:" << p2.sNum << endl;

    //3. 静态成员也有访问权限,类外不能访问私有成员
    //cout << "Person::sOther:" << Person::sOther << endl;
    Person p3;
    //cout << "p3.sOther:" << p3.sOther << endl;

    return 0;
}

程序输出:

shell
Person::sNum:100
p1.sNum:200
p2.sNum:200

静态成员函数

在类定义中,前面有static说明的成员函数称为静态成员函数。静态成员函数使用方式和静态变量一样,同样在对象没有创建前,即可通过类名调用。静态成员函数主要为了访问静态变量,但是,不能访问普通成员变量

静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。

代码演示
cpp
#include <iostream>
using namespace std;

class Person {
public:
    //普通成员函数可以访问static和non-static成员属性
    void changeParam1(int param) {
        mParam = param;
        sNum = param;
    }

    //静态成员函数只能访问static成员属性
    static void changeParam2(int param) {
        //mParam = param; //无法访问
        sNum = param;
    }

private:
    static void changeParam3(int param) {
        //mParam = param; //无法访问
        sNum = param;
    }

public:
    int mParam;
    static int sNum;
};

//静态成员属性类外初始化
int Person::sNum = 0;

int main(int argc, char *argv[]) {
    //1. 类名直接调用
    Person::changeParam2(100);

    //2. 通过对象调用
    Person p;
    p.changeParam2(200);

    //3. 静态成员函数也有访问权限
    //Person::changeParam3(100); //类外无法访问私有静态成员函数
    //Person p1;
    //p1.changeParam3(200);

    return 0;
}

静态成员实现单例模式

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

:在单例类的内部实现只生成一个实例,同时它提供一个静态的getInstance()工厂方法,让客户可以访问它的唯一实例;为了防止在外部对其实例化,将其默认构造函数和拷贝构造函数设计为私有;在单例类内部定义了一个Singleton类型的静态对象,作为外部共享的唯一实例。

单例模式实现流程

  1. 在类内将构造函数和拷贝构造函数私有化,以保证在类外无法创建类对象;
  2. 私有化一个该类的对象指针,同时在类外将该指针在堆区创建出来(这一过程也叫静态成员类外初始化)
  3. 在类内实现一个静态成员方法(静态成员方法才能访问静态成员变量)返回私有的对象指针

用单例模式,模拟公司员工使用打印机场景,打印机可以打印员工要输出的内容,并且可以累积打印机使用次数。

点击查看代码
cpp
#include <iostream>
using namespace std;

class Printer {
public:
    static Printer* getInstance() { return pPrinter; }
    void PrintText(string text) {
        cout << "打印内容:" << text << endl;
        cout << "已打印次数:" << mTimes << endl;
        cout << "--------------" << endl;
        mTimes++;
    }
private:
    Printer() { mTimes = 0; }
    Printer(const Printer&) {}
private:
    static Printer* pPrinter;
    int mTimes;
};

Printer* Printer::pPrinter = new Printer;

void test() {
    Printer* printer = Printer::getInstance();
    printer->PrintText("离职报告!");
    printer->PrintText("入职合同!");
    printer->PrintText("提交代码!");
}

int main(int argc, char *argv[]) {
    test();

    return 0;
}

程序输出:

shell
打印内容:离职报告!
已打印次数:0
--------------
打印内容:入职合同!
已打印次数:1
--------------
打印内容:提交代码!
已打印次数:2
--------------

const成员

const修饰的成员变量

const修饰的成员变量与普通成员变量的区别就是常量和变量的区别。具体可以回顾一下 以往的内容

const修饰的成员函数-常函数

在类的成员函数后面添加const关键字,该成员函数就称为了一个常量成员函数,有些地方称为常函数。形式如下

cpp
class Test{
public:
    void show() {}   // 普通成员函数
    void show() const {}   // 常函数
};
  • 用const修饰的成员函数时,
  • 当成员变量类型符前用mutable修饰时例外
代码演示
cpp
#include <iostream>
using namespace std;

//const修饰成员函数
class Person {
public:
    Person() {
        this->mAge = 0;
        this->mID = 0;
    }

    //在函数括号后面加上const,修饰成员变量不可修改,除了mutable变量
    void sonmeOperate() const {
        //this->mAge = 200; //mAge不可修改
        this->mID = 10;
    }

    void ShowPerson() {
        cout << "ID:" << mID << " mAge:" << mAge << endl;
    }

private:
    int mAge;
    mutable int mID;
};

int main(int argc, char *argv[]) {
    Person person;
    person.sonmeOperate();
    person.ShowPerson();

    return 0;
}

程序输出:

shell
ID:10 mAge:0

提示

如果成员函数内不修改该对象中的成员属性,那么推荐将该成员函数实现为常函数,这样普通对象和常对象都可以调用该方法。否则,常对象无法调用普通成员方法。

const修饰的对象-常对象

定义类对象时使用了const关键字修饰,这个对象就被称为常对象。形式如下

cpp
Perosn p1;  // 普通类对象
const Perosn p1;  // 常对象
  • 常对象只能调用常函数(const修饰的成员函数)
  • 常对象可访问 const 或非 const 数据成员,不能修改,除非成员用mutable修饰
代码演示
cpp
#include <iostream>
using namespace std;

class Person {
public:
    Person() {
        this->mAge = 0;
        this->mID = 0;
    }

    void ChangePerson() const {
        //mAge = 100;   //不可修改
        mID = 100;
    }

    void ShowPerson() {
        this->mAge = 1000;
        cout << "ID:" << this->mID << " Age:" << this->mAge << endl;
    }

public:
    int mAge;
    mutable int mID;
};

void test() {
    const Person person;

    //1. 可访问数据成员
    cout << "Age:" << person.mAge << endl;
    //person.mAge = 300; //不可修改
    person.mID = 1001; //但是可以修改mutable修饰的成员变量

    //2. 只能访问const修饰的函数
    //person.ShowPerson();
    person.ChangePerson();
}

int main(int argc, char *argv[]) {
    test();

    return 0;
}

程序输出:

shell
Age:0

const静态成员属性

如果一个类的成员,既要实现共享,又要实现不可改变,那就用 static const 修饰。

代码演示
cpp
#include <iostream>
using namespace std;

class Person {
public:
    //static const int mShare = 10;
    const static int mShare = 10; //只读区,不可修改
};

int main(int argc, char *argv[]) {
    cout << Person::mShare << endl;
    //Person::mShare = 20;

    return 0;
}

程序输出:

shell
10

不抛异常的成员

我们可以使用noexcept关键词来修饰一个函数,表明该函数不抛出异常 形如

cpp
int add(int a, int b) noexcept {
    return a + b;
}

noexcept关键词是显式的指出该函数不抛出异常,但是编译器并不做检查,如果你在该函数中抛出异常了,依然可以编译通过但是运行时会导致程序出错。

cpp
#include <iostream>

int add(int a, int b) /*noexcept*/ {
    if (a < 0) {
        throw "a小于0";
    }

    return a + b;
}

int main() {
    try {
        int ret = add(-1, 2);
        std::cout << ret << std::endl;
    } catch (const char *) {
        std::cout << "add函数抛出异常了" << std::endl;
    }

    return 0;
}

如果注释掉noexcept关键词编译运行程序可以正常捕获被抛出的异常。运行结果为

bash
add函数抛出异常了

但是如果不注释noexcept关键词编译运行程序,程序会直接崩溃。运行结果为

bash
terminate called after throwing an instance of 'char const*'
Aborted (core dumped)

那么如果使用noexcept关键词来修饰类中的成员函数,与上面是一样的效果。

点击查看示例
cpp
#include <iostream>

class Test {
public:
    void show() /*noexcept*/ {
        std::cout << "call Test::show()" << std::endl;

        throw 123;
    }
};

int main() {
    Test t;
    
    try {
        t.show();
    } catch (int) {
        std::cout << "show函数抛出异常了" << std::endl;
    }

    return 0;
}

如果注释掉noexcept关键词编译运行程序可以正常捕获被抛出的异常。运行结果为

bash
call Test::show()
show函数抛出异常了

但是如果不注释noexcept关键词编译运行程序,程序会直接崩溃。运行结果为

bash
call Test::show()
terminate called after throwing an instance of 'int'
Aborted (core dumped)

参考资料