类和对象

C++是一门面向对象的语言,提到面向对象就不得不提到三大特点:封装、继承、多态。

首先来看第一大特点:封装

封装


在C++中类使用关键字class修饰 class [类名]{ }

举个例子:

以学生类为例


class Students{
//权限
public:
    //类中的属性和行为都成为成员,成员变量、成员属性
    //成员函数、成员方法。
    
    //属性
    string m_name;//姓名
    int m_id;//学号
    
    //行为
    void showStuden(){

        cout<< "学号:"<<m_id  << "姓名" << m_name<<endl;
        }

我们可以看到一个类里,包含了权限,属性,行为。

通俗的理解:

权限指的是类内部的成员的权限。这个成员可以是变量即成员变量或者是成员函数。属性指的是类的内部所需要的一些特性,或者说是字段,比如学生的姓名,学号,员工的工号,手机号等等诸如此类。行为指的是一些函数或者方法,来操纵这个类的成员。

假设我们通过类以及函数封装来实现一个求圆的周长和面积,应该怎么写:


const double PI = 3.14;

class Circle{

    //访问权限
public:

    //属性
    double m_r;

    //行为
    double calculateZC(){
        return 2*PI*m_r;
    }
    double calculateMJ(){
        return PI*m_r*m_r;
    }

};

其中,PI代表圆周率,是个常量,我们就用const来修饰,double类型。

然后不论是求面积还是周长,都需要圆的一个属性,那就是半径,所以我们需要给这个类的属性中一个半径r。

最后分别实现一个计算周长和面积的行为,也就是函数:calculateZC()calculateMJ()

面向对象中,类还是比较容易理解的,主要是通过抽象将一类事物统一的归为一个类,由一个类来管理操作。在实际使用时,我们需要实例化这个类,因为类是抽象的,是一个比较大的范围,但是用的时候,我闷需要创建一个对象,这个对象属于这个类,实际操纵的是这个对象,而非这个类。

访问权限

 

类在设计时,可以把属性和行为放在不同的权限里,加以控制,访问权限有三种:

  • 访问权限类型:
  • public:公共权限
  • protected 保护权限
  • private 私有权限

  • 关于public:不仅类的内部成员之前可以访问,类的外部也可以访问。
  • 关于protected :类内部可以访问,类外部不能访问。子类可以访问父类中的保护内容
  • 关于private :类内部可以访问,类外部不可访问,子类也不可以访问父类中的私有内容

以学生类为例


class Students{

public:
    //类中的属性和行为都成为成员,成员变量、成员属性
    //成员函数、成员方法。
    //属性
    string m_name;//姓名
    int m_id;//学号
    //行为
    void showStuden(){

        cout<< "学号:"<<m_id  << "姓名" << m_name<<endl;
        }
    void setName(string name){
        m_name=name;

    }
    void  setId(int id){
        m_id = id;
    }

};

这里给学生类中的成员均是public也就是公共权限,那就是说类内部可以访问,类外部也可以访问。

举个例子:

假设目前有个person类,包含公共权限的属性:姓名,包含保护权限的属性:房子、车子,包含私有属性的银行卡密码。

class Person{

public:
    string m_Name;//姓名

protected:
    string m_House;//房子
    string m_Car;//车子


private:
    int m_password;//银行卡密码


    //方法

public:
    void func(){
        m_Name="张三";
        m_Car="拖拉机";
        m_House="别墅";
        m_password=123456;

    }
};

int main(){
    Person p1;
    p1.m_Name="李四";
    p1.m_Car="法拉利";//protected在Persion类外就是无法访问的
    p1.m_password=421331;//同样的private 在Persion类外也是无法访问的
}

那么这样的话 ,我们可以看到在main函数中,姓名字段是可以用的。但是车和密码字段是不能直接在main函数中使用的,因为脱离了person类的访问。

同样对于方法func(),其中包含了一些保护权限和私有权限的属性,但是这个方法是在Person类内部的,但它同时又是public权限的,所以main函数中可以访问func这个函数(即便其中包含了私有属性)。


struct和class区别

在C++中,struct和class的唯一区别就是默认的访问权限不同,struct默认权限是公共,class默认权限是私有。


class C1{

    //一个权限都不给。就是默认权限

    int m_A;
};

struct C2{
    int  m_A;
};
int main(){

    //struct 默认权限是public
    //class 默认权限是private
    C1 c1;
    c1.m_A=100;//无法访问
    C2 c2;
    c2.m_A=100;//不报错
}

我们给出一个类 C1,给出一个struct C2,都定义了一个int类型的m_A,在main函数中c1的m_A是无法访问的,c2的m_A是可以访问修改的。


成员属性设置为私有


成员属性设置为私有的目的是,可以自己控制读写权限。有些成员你可以自行设置成可读可写或者仅读,仅写。并且对于需要修改的也就是写,可以检测数据有效性。

自由设置权限

例如:

class Person{
public:
    //可写
    void  setName(string name){
        m_Name=name;
    }
    //可读
    string getName(){
        return  m_Name;
    }

    int getAge(){
        return m_Age;
    }
    void setIdol(string idol){
        m_idol=idol;
    }
private:

    string  m_Name;//可读写

    int m_Age=20;//只读

    string m_idol;//只写


};

上面这个例子。就对name设置了可读可写,对年龄设置了仅读,对idol设置了仅写权限。

通过成员方法,来间接控制成员属性的权限。默认所有的属性都是私有,这样一来比较容易控制其权限分配。

总结:只读就是get,只写就是set,可读写就是既有get又有set。这个在开发中是经常使用的。

检测数据有效性

还是上面的例子,假设我们要对年龄的设置加一个限制,要输入0~100岁之间,应如何去做。

class Person{
public:
    //可写
    void  setName(string name){
        m_Name=name;
    }
    //可读
    string getName(){
        return  m_Name;
    }

    int getAge(){
        return m_Age;
    }
    void setAge(int age){
        if (age>=0&&age<=100)
            m_Age=age;
        else
            cout<<"年龄设置不规范,请设置在0~100岁"<<endl;
    }
    void setIdol(string idol){
        m_idol=idol;
    }
private:

    string  m_Name;//可读写

    int m_Age;//可读可写,但是写的必须是0~100之间

    string m_idol;//只写


};

这里就需要写一个set函数,首先让年龄可以写,然后对要设置的年龄加一个限制。

封装的案例

案例1

设置一个立方体类,能够求其表面积和体积,并且写出全局函数和成员函数来比较两个立方体是否相同。


#include "iostream"


using namespace std;

class Cube{
public:
    //成员函数
//全部可读写:
    void setChang(double chang){
        if (chang<=0){
            cout<<"立方体的长设置有误,必须大于0"<<endl;
            return;
        }
        else
             m_Chang=chang;
    }
    double getChang(){
        return m_Chang;
    }

    void setKuan(double kuan){
        if (kuan<=0){
            cout<<"立方体的宽设置有误,必须大于0"<<endl;
            return;
        }
        else
        m_Kuan=kuan;
    }
    double getKuan(){
        return m_Kuan;
    }

    void setGao(double gao){
        if (gao<=0){
            cout<<"立方体的高设置有误,必须大于0"<<endl;
            return;
        }
        else
        m_Gao=gao;
    }
    double getGao(){
        return m_Gao;
    }
    //求面积
    double CalcMJ(){
        if (m_Chang>0&&m_Kuan>0&&m_Gao>0)
        return 2*(m_Chang*m_Kuan+m_Chang*m_Gao+m_Kuan*m_Gao);
        else{
            cout<<"长宽高均不能为0"<<endl;
        }
    }
    //求体积
    double CalcTJ(){
        if (m_Chang>0&&m_Kuan>0&&m_Gao>0)
        return m_Gao*m_Kuan*m_Chang;
        else{
            cout<<"长宽高均不能为0"<<endl;
        }
    }
    //利用成员函数判断是否相等
    bool isSameByClass(Cube c2){
        if(m_Chang==c2.getChang()
            &&m_Gao==c2.getGao()
           &&m_Kuan==c2.getKuan())
            return true;
        else
            return false;

    }

private:
    double m_Chang=1;
    double m_Kuan=1;
    double m_Gao=1;
};

//全局函数
bool isSame(Cube &c1,Cube &c2){
    if(c1.getChang()==c2.getChang()
    &&c1.getGao()==c2.getGao()
    &&c1.getKuan()==c2.getKuan())
        return true;
    else
        return false;

}
int main(){
    Cube cube1;
    cube1.setChang(5.5);
    cube1.setKuan(6.2);
    cube1.setGao(7.5);
    cout<<"立方体的表面积为"<<cube1.CalcMJ()<<endl;
    cout<<"立方体的体积为"<<cube1.CalcTJ()<<endl;
    Cube cube2;
    cube2.setChang(5.5);
    cube2.setKuan(6.2);
    cube2.setGao(7.5);

    bool ret= isSame(cube1,cube2);
    if (ret){
        cout<<"两个立方体相等"<<endl;
    }else{
        cout<<"两个立方体不相等"<<endl;

    }
    bool retClass=cube1.isSameByClass(cube2);
    if (retClass){
        cout<<"两个立方体相等"<<endl;
    }else{
        cout<<"两个立方体不相等"<<endl;

    }
    return 0;
}

其中,对于长宽高的判断,其实只需要在单独设置里判断即可,只要单独的设置了判断,那么面积体积处就不需要设置判断了。

另外,可以看出如果是在类外的全局函数,来比较两个立方体是否相等,我闷需要传入,两个对象,这里使用了引用,不需要额外拷贝,如果使用了成员函数的话,我们可以直接在类内访问到私有权限的属性,这样只需要传入相比较的对象即可,稍微简化了一步骤


案例2

点和圆的关系,设计一个圆类,和一个点类,计算圆和点之间的关系。

其中一种解法(我自己写的,如有问题,请留言):


//令坐标轴原点为圆心,作圆。
class Circle{
public:
    double getR(){
        return m_R;
    }
    void setR(double r){
        m_R=r;
    }
private:
    double m_R=1;
};

class Point{
public:
    double getX(){
        return m_X;
    }
    void  setX(double x){
        m_X=x;
    }
    double getY(){
        return m_Y;
    }
    void  setY(double y){
        m_Y=y;
    }
    void CircleAndPoint(Circle circle){
        //求出点相对于坐标轴也就是圆心的距离
        //与半径做比较,如果大于半径,那就在圆外
        //如果等于半径,那就在圆上
        //如果小于半径,那就在圆内
        double XFang=abs(m_X)*abs(m_X);
        double YFang=abs(m_Y)*abs(m_Y);
        double distance= sqrt(XFang+YFang);
        if (circle.getR()>distance){
            cout<<"点在圆内"<<endl;
        }else if(circle.getR()==distance){
            cout<<"点在圆的边上"<<endl;
        }else{
            cout<<"点在圆外"<<endl;
        }
    }

private:
    //默认坐标原点
    double m_X=0;
    double m_Y=0;
};

int main(){
    Circle c1;
    c1.setR(5);
    Point p1;
    p1.setX(3);
    p1.setY(4);
    p1.CircleAndPoint(c1);
    return 0;
}

当然这种方式是默认圆心,是在原点,那当然还有可能圆心不在原点。应当如何做呢?

class Point{
public:
    double getX() const {
        return m_X;
    }
    void setX(double x) {
        m_X = x;
    }
    double getY() const {
        return m_Y;
    }
    void setY(double y) {
        m_Y = y;
    }

private:
    // 默认坐标原点
    double m_X = 0;
    double m_Y = 0;
};

class Circle{
public:
    double getR() const {
        return m_R;
    }
    void setR(double r) {
        m_R = r;
    }
    Point getCenter() const {
        return m_Center;
    }
    void setCenter(Point center) {
        m_Center = center;
    }

private:
    double m_R = 1;
    Point m_Center;
};

void CircleAndPoint(const Circle& circle, const Point& point) {
    Point center = circle.getCenter();
    double XDistance = (point.getX() - center.getX()) * (point.getX() - center.getX());
    double YDistance = (point.getY() - center.getY()) * (point.getY() - center.getY());
    double distance = sqrt(XDistance + YDistance);
    if (circle.getR() > distance) {
        cout << "点在圆内" << endl;
    } else if (circle.getR() == distance) {
        cout << "点在圆的边上" << endl;
    } else {
        cout << "点在圆外" << endl;
    }
}

int main() {
    Point center;
    center.setX(3);
    center.setY(2);

    Circle c1;
    c1.setR(5);
    c1.setCenter(center);

    Point p1;
    p1.setX(3);
    p1.setY(4);

    CircleAndPoint(c1, p1);

    return 0;
}

对象的初始化和清理

C++的面向对象也是来源于生活,生活中我们需要什么东西都会获取也就是初始化,不需要后就会丢掉,也就是清理,c++中也同样如此,每个对象都有自己的初始化和清理工作。

构造函数和析构函数

对象的初始化和清理是比较重要的安全问题,一个对象或者变量没有初始状态,对其使用的后果是未知的,同样,使用完一个对象和变量,没有及时清理,也会造成一定的安全问题。

在C++中利用了构造函数析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象的初始化和清理工作,这是编译器强制要求我们去做的事,因此如果我们不提供构造和析构,编译器会提供构造函数和析构函数是空实现。

构造函数:主要作用是在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。

析构函数:主要作用在对象销毁前系统自动调用,执行清理工作。

构造函数语法:类名(){}

1.构造函数,没有返回值,也不需要写void

2.函数名与类名相同

3.构造函数可以有参数,因此可以发生重载

4.程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次。

析构函数语法:~类名(){}

1.析构函数,没有返回值,也不需要写void

2.函数名称与类名相同,在名称前加上符号~

3.析构函数不可以有参数,因此不可能发生重载

4.程序在销毁前会自动调用析构,无需手动调用,而且只会调用一次。

举个例子

class Person{
public:
    Person(){
        cout<<"Person的构造函数调用"<<endl;
    }
    ~Person(){
        cout<<"Person的析构函数调用"<<endl;

    }
};
//
int main(){
    Person person;//栈上的数据,test01执行完毕后,会释放

    return 0;
}

这里我们会直接得到输出是:

C++学习笔记(三)类和对象-封装_构造函数


C++学习笔记(三)类和对象-封装_析构函数_02

这里我们并没有去调用这个函数,但是其内容就是被调用过了。说明这个对象是会自动初始化和释放的。

如果我们自己不写构造函数和析构函数,编译器自动帮我们写了,只不过函数体是空的。

构造函数的分类以及调用

按照参数分类:

有参构造和无参构造

按类型分类:

普通构造和拷贝构造

三种调用方式:

括号法、显示法、隐式转换法

分类

首先是分类:举个例子来看

//构造函数分类
class Person{
public:
    //无参构造
    Person(){
        cout<<"Person的无参构造函数调用"<<endl;

    }
    //有参构造
    Person(int  a){
    age=a;
        cout<<"Person的有参构造函数调用"<<endl;

    }

    //按照类型分上面的都是普通构造函数
    //还有一种是拷贝构造函数

    Person(const Person &p){//特点是传进来的不能修改,并且要以引用方式传递
        age=p.age;//将传入的对象所有的属性都拷贝到本身
        cout<<"Person的拷贝构造函数调用"<<endl;

    }
    //基本只要不是拷贝构造剩余的构造均属于普通构造
    //析构函数不能有参数
    ~Person(){
        cout<<"Person的析构函数调用"<<endl;
//
    }



//private:
    int age;
};

我们可以看到普通的构造类型,无论是否有参,都比较好理解,但是针对拷贝构造函数,它的格式需要稍微注意,因为是拷贝构造,所以传进来被拷贝的对象一定不能修改,要加const,另外需要使用引用传参数。

调用

调用同样根据上面的例子中我们给出的构造函数来分析。

//调用
void test(){
//括号法 (普通构造调用)

//注意::调用默认构造函数的时候,不要加(),因为编译器可能会认为是一个函数声明
//而不是在创建对象。
Person p;
Person p2(10);
//(拷贝构造函数)
Person p3(p2);
cout<<"p2的年龄为"<<p2.age<<endl;

cout<<"p3的年龄为"<<p3.age<<endl;

//显示法
Person p5;//无参
Person p6 =Person(10);//有参
Person p7=Person(p6);//拷贝构造
//Person(10)是匿名对象,=号前就是他的名,此行执行完 系统会立刻回收匿名对象
//注意:不要利用拷贝构造函数来初始化匿名对象。例如 Person(p6);
//Person(p6);会出现重定向错误,重定向到p6了

//隐士转换法

Person p8=10;//相当于写了Person p8 =Person(10); 有参构造调用 
Person p9=p8;//拷贝构造调用相当于Person p9 = Person(p8 )

}

可以看出三种方法的调用方式,相对来说还是比较多的。


其中需要注意的是:

1.调用默认构造函数的时候,不要加(),因为编译器可能会认为是一个函数声明而不是在创建对象。

2.不要利用拷贝构造函数来初始化匿名对象。会出现重定向错误,编译器会认为这是你写了第二遍的上面某个构造函数


拷贝构造函数的调用时机

C++中拷贝构造函数调用时机通常有三种情况

1.使用一个已经创建完毕的对象来初始化一个新对象(复制)

void test01(){
    Person p1(20);
    Person p2(p1);
    cout<< "p2 Age"<< p2.m_Age<<endl;
}

2.值传递的方式给函数参数传值

void  doWork(Person p){//值传递会拷贝一份新的,所以这里会是拷贝构造函数

};
void test02(){
    Person p;
    doWork(p);
}

3.以值方式返回局部对象

Person dowork2(){
    Person p1;
    cout<<(int *)&p1<<endl;
    return p1;//值方式 返回同样会拷贝出来一份新的构造函数。

}
void test03(){
    Person p=dowork2();
    cout<<(int* )&p<<endl;

}

构造函数的调用规则

默认情况下,C++至少给一个类添加三个函数

1.默认构造函数(无参数,函数体为空)

2.默认析构函数(无参,函数体为空)

3.默认构造函数,对属性进行值拷贝


值得注意的规则是:

如果用户定义有参构造函数,c++不在提供默认无参构造,但会提供默认拷贝构造

如果用户定义拷贝构造函数,c++不会在提供其他构造函数



class Person{
public:

    Person(){
        cout<<"默认构造"<<endl;
    }

    Person(int age){
        m_Age=age;
        cout<<"有参数构造"<<endl;

        }
        Person(const Person &p){
        cout<<"Person的拷贝构造函数调用"<<endl;
        m_Age=p.m_Age;
    }
    ~Person(){
        cout<<"析构"<<endl;

    }
    int m_Age;

};
void test01(){
    Person p1;
    p1.m_Age=19;
    Person p2(p1);
    cout<< "P2的年龄为"<<p2.m_Age <<endl;

}
int  main(){
    test01();
    return 0;
}


以上面的代码为例子,其输出结果是:

C++学习笔记(三)类和对象-封装_构造函数_03

如果将自己写的拷贝构造函数注释掉的话会是什么结果呢?

class Person{
public:

    Person(){
        cout<<"默认构造"<<endl;
    }

    Person(int age){
        m_Age=age;
        cout<<"有参数构造"<<endl;

        }
//        Person(const Person &p){
//        cout<<"Person的拷贝构造函数调用"<<endl;
//        m_Age=p.m_Age;
//    }
    ~Person(){
        cout<<"析构"<<endl;

    }
    int m_Age;

};

再一次运行输出结果是:

C++学习笔记(三)类和对象-封装_构造函数_04

可以看到少了一句我们自己写的输出,但是年龄仍然为19,说明其拷贝成功了。所以结论是:如果我们自己提供了拷贝构造,那么编译器就不会为我们提供了,如果我们没有写,编译器会默认提供空的拷贝函数,里面只有对所有的属性进行拷贝。


深拷贝和浅拷贝

浅拷贝:简单的赋值操作

深拷贝:在堆区重新申请空间,进行拷贝操作

通过下面案例分析一下:

class Person{
public:

    Person(){
        cout<<"默认构造"<<endl;
    }

    Person(int age,double height){
        m_Age=age;
        m_Height=new double (height);//堆区开辟的空间由程序员开辟,也有程序员回收
        //
        cout<<"有参数构造"<<endl;

    }
//        Person(const Person &p){
//        cout<<"Person的拷贝构造函数调用"<<endl;
//        m_Age=p.m_Age;
//    }
    ~Person(){
        //析构代码的作用通常来说会将堆区开放的数据作释放操作,
        if(m_Height !=NULL){
            delete m_Height;
            m_Height=NULL;
        }
        cout<<"析构"<<endl;

    }
    int m_Age;
    double * m_Height;//堆区

};
void test01(){
    Person p1(10,1.8);
    cout<<"p1年龄"<<p1.m_Age<<"身高为" <<*p1.m_Height<<endl;
    Person p2(p1);
    cout<<"p2年龄"<<p2.m_Age<<"身高为" <<*p2.m_Height<<endl;
//    cout<<"p2年龄为"<<p2.m_Age<<endl;


}

这里我们调用test01这个函数后,是会报错的。

C++学习笔记(三)类和对象-封装_析构函数_05

我们在Person类中提供了一个age 以及在堆区new了一个height,如果我们利用Person p2(p1),这是编译器提供的拷贝构造函数,因为是值拷贝,只会做浅拷贝操作。如果只是age那没问题,但是height是一个指针,值拷贝只会把指针地址原样拷贝过去。

构造函数执行完毕之后,会调用析构函数,释放,按照先进后出的原则,p2会先被释放,因此height的原本的地址就会被delete,就是释放过了,如果p1还要在执行一次释放,就会出问题了(非法操作)

这时就需要我们去深拷贝来解决这个问题,深拷贝,重新在堆区开辟一段空间去保存身高,这样p1有一份身高,p2也有一份身高,只不过身高的值是一样的,但是指向这个值的地址是不同的,因此各自在释放的时候就不会出现非法问题了。

那么我们需要自己做一个拷贝构造函数,

    Person(const Person &p){
        cout<<"Person的拷贝构造函数调用"<<endl;
        m_Age=p.m_Age;
//        m_Height=p.m_Height;//如果是编译器默认实现就是这样。
    //深拷贝一次
    m_Height=new double (*p.m_Height);
    }

来代替默认的拷贝构造函数,这样就解决了浅拷贝的问题。

实际上就是我们要重新开辟一段空间,来存放要拷贝堆区上的值。


初始化列表


C++中提供了初始化列表语法,用来初始化属性

语法:构造函数():属性1(值1),属性2(值2)....

class Person{
public:
    //传统初始化操作

//    Person(int a,int b,int c){
//        m_A=a;
//        m_B=b;
//        m_C=c;
//
//    }

    //利用初始化列表来初始化
    Person(int a,int b,int c):m_A(a),m_B(b),m_C(c){}

    int m_A;
    int m_B;
    int m_C;



};

类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员

class A{};
class B{

    A a;
};

其中,B类中有对象A作为成员,A为对象成员。

但是这印出了一个问题,当创建B对象时,A与B的构造和析构顺序是如何的呢?

以如下例子


class Phone{



public:
    Phone(string brand){
        m_Brand=brand;
                cout<<"Phone的构造函数调用"<<endl;

    }
    string m_Brand;
};
class Person{

public:
    //Phone phone=pName 隐式转换法
    Person(string name,string brand):  m_Name(name), m_Phone(brand){
                cout<<"Person的构造函数调用"<<endl;

    }
    string m_Name;
    Phone  m_Phone;
};

void test01(){
     Person p("张三","XiaoMi");
     cout<<p.m_Name<<"拿着"<< p.m_Phone.m_Brand<<"手机  "<<endl;
}

到底是先执行了Person的构造函数还是先执行了Phone的构造函数呢?

运行结果是:

C++学习笔记(三)类和对象-封装_封装-对象_06

那也就意味着对象成员会先执行构造函数。

总结:当其他类的对象作为本类的成员,先构造其他类,在构造本身。析构的执行顺序和构造正好是相反的,便于理解我们可以参考先进后出的原则。

静态成员

静态成员就是在成员变量和成员函数前面加上关键字static,称为静态成员,静态成员分为:

1.静态成员变量:

所有对象共享一份数据

在编译阶段分配内存

类内声明,类外初始化


举个例子:

class Person{
public:

    //静态成员变量
    //1.所有对象共享同一份数据
    //2.编译阶段分配内存
    //3. 类内声明,类外初始化
    static int m_A;

};
 int Person::m_A=100;//类外初始化,但是要明确作用域

void  test01(){
       //访问方式1
    Person p;
    cout<<p.m_A<<endl;
    Person p2;
    p2.m_A=200;
    cout<<p.m_A<<endl;//结果是200 说明是共享同一份数据,我们是把100给改了。

    cout<<Person::m_A<<endl;//访问方式2

}

声明静态成员变量,其不属于某个对象,所有对象共享同一份数据,一个改就全改了。因此其有两种访问方式:1.通过对象访问2.通过类名访问

静态成员同样是具有权限的,可以自己设定其权限。

2.静态成员函数:

所有对象共享同一个函数

静态成员函数只能访问静态成员变量

class Person{

    //静态成员函数
    //所有对象共享同一个函数
    //静态成员函数只能访问静态成员变量

public:
    static void pri(){

        cout<< m_Age <<endl;
//        cout<< m_phoneNumber <<endl;

    }

    static int m_Age;
    int m_phoneNumber;
};
 int Person::m_Age=85;

void  test01(){

    Person p;
    p.pri();
    Person::pri();//通过类名直接访问,说明他不属于某个对象。

}

上述例子给出了静态成员函数pri(),m_Age是静态成员变量,phoneNumber为普通成员变量。

静态成员函数只能输出m_Age的值,如果输出m_phoneNumber的值就会报错。

这个原因主要是因为静态成员函数只有一份,但是非静态成员变量,需要通过变量去访问。所以静态成员函数并不知道要访问的是那个对象的成员变量。

当然,静态成员函数同样是有权限的