c++中的静态成员与非静态成员

目录

一、静态成员     

二、静态成员变量

1. 静态成员变量的性质

2. 静态成员变量的访问

三、静态成员函数

 四、静态成员编程练习

1. 【例】统计对象个数

2. 【例】银行账户

五、成员与对象大小的关系

六、空指针访问成员函数

七、常函数和常对象

1.常函数

2.常对象


一、静态成员     

         静态成员是在成员变量和成员函数前面加上关键字static。

二、静态成员变量

1. 静态成员变量的性质

        作为静态成员变量,符合以下特点:

  • 所有对象共享同一份数据;
  • 在编译阶段分配内存,在主函数之前进行构造;
  • 类内声明,类外初始化;
  • 存放在静态区,不占用对象内存;
  • 在发生继承时,静态成员变量不会被继承,父类子类共享同一个静态成员。
#include<iostream> using namespace std; class Person { public: static int a; Person() { this->a++; } }; //类外初始化 int Person::a = 1; int main() { //所有对象共享一份数据 Person a1, a2, a3, a4; cout << a1.a << " " << a2.a << " " << a3.a << " " << a4.a << endl;//输出 5 5 5 5 //存放在静态区,不占用对象内存 cout << sizeof(a1); //输出 1 return 0; }

        其中,代码将静态变量a初始化为1,在无参构造函数中,通过自增变量进行初始化,按理来说创建四个类的对象a1a2a3a4,每次都会调用无参构造方法,这样分别输出四个对象的变量a,得到的值应该是2 3 4 5才对,那为什么实际输出的结果不是这样呢?

        这就是因为“针对静态变量而言,所有对象都会共享同一份数据”,所以静态变量a的最终态(a=5)是所有对象时刻共享的。

2. 静态成员变量的访问

        两种方式:1.通过对象访问;2.通过类名访问

class Person { public: static int a; }; int main() { Person a1; //通过对象访问 a1.a = 100; //通过类名访问 Person::a = 100; }

三、静态成员函数

        作为静态成员函数,存在以下性质:

  • 所有对象共享同一个函数;
  • 静态成员函数只能访问静态成员变量;
  • 没有this指针,所以静态成员函数内部不能访问成员变量和成员函数。

注:这是因为静态成员函数中没有this指针,也就不能区分哪个变量属于哪个对象,进而不能访问非静态成员变量。

        据上述几条性质,分别对下面代码中的语句1、2、3进行分析如下:

#include<iostream> using namespace std; class Person { static int a; int b; void work() { } static void fun() { a = 100; //1 b = 100; //2 work(); //3 cout << "fun" << endl; } };

语句1:正常执行,因为静态成员函数能够访问静态成员变量

语句2:报错,因为静态成员函数Person中没有this指针,不能访问非静态成员变量

语句3:报错,静态成员函数不能调用非静态成员函数,因为无法给work()中的隐藏参数this传参。

 四、静态成员编程练习

1. 【例】统计对象个数

设计一个类Counter,要求:

  • (1)使用静态成员变量来记录对象的数量;
  • (2)提供静态成员函数来获取当前对象的数量。
#include<iostream> using namespace std; class Counter { public: static int num; Counter() { num++; cout << "创建对象,当前总数:" << num << endl; } ~Counter() { num--; cout << "销毁对象,剩余总数:" << num << endl; } static int getNum() { return num; } }; int Counter::num = 0; int main() { Counter c1, c2, c3, c4, c5; cout << "块内对象数:" << Counter::getNum() << endl; return 0; }

        程序如上,这里需要注意的点如下:

  1. 调用析构函数的时候,对象会自动销毁,所以需要添加对象减少的逻辑;
  2. 静态成员变量的初始化应该在类外。

2. 【例】银行账户

设计一个BankAccount类,模拟银行账户的基本操作:

  • (1)使用静态成员变量记录所有账户的总余额,每个账户对象有自己的余额属性;
  • (2)每个账户能够存取;
  • (3)提供静态成员函数来查询余额;
  • (4)在构造函数和析构函数中更新总余额。
#include<iostream> using namespace std; class BankAccount { public: static double sum; BankAccount(){} BankAccount(double sum) { this->sum = sum; } void addA(double sum) { if (sum > 0) { cout << "成功存入" << sum << "元" << endl; this->sum += sum; } else { cout << "存储失败" << endl; return; } } void disaddA(double sum) { if (this->sum >= sum) { cout << "成功取出" << sum << "元" << endl; this->sum -= sum; } else { cout << "取出失败,余额不足" << endl; return; } } static void getAInf() { cout << "余额为:" << sum << "元" << endl; } ~BankAccount() { cout << "余额为:" << sum << endl; } }; double BankAccount::sum = 0; int main() { BankAccount b1; b1.addA(100); b1.disaddA(78.5); b1.getAInf(); return 0; }

运行结果如下图:

五、成员与对象大小的关系

        在c++中,类里的成员变量和成员函数是分开存储的,只有非静态成员变量才属于类的对象上。也就是说,非静态成员函数、静态成员函数、静态成员变量都不属于类的对象中。可以通过直接输出sizeof(对象)来验证该结论,这里不做赘述。

六、空指针访问成员函数

        在c++中,空指针也是能调用成员函数的,但是要注意有没有用到this指针,如果用到this指针,就需要加以判断来保证代码的健壮性

        通过下面代码能够看到空指针访问成员函数的两种不同情况的区别:

#include <iostream> class MyClass { public: void safeMethod() { } void unsafeMethod() { std::cout << "Value: " << value << std::endl; // 危险:访问成员变量 } private: int value = 42; }; int main() { MyClass* ptr = nullptr; // 安全调用:空对象不访问成员变量 ptr->safeMethod(); // 可以正常运行 // 危险调用:访问成员变量 ptr->unsafeMethod(); // 运行时崩溃 return 0; }

        在成员函数unsafeMethod()中使用了成员变量value(使用了this指针),程序崩溃。

        为了避免这种情况的发生,需要事先判断是否为空指针,再继续下面的程序,如下:

#include <iostream> using namespace std; class MyClass { public: void safeMethod() { } void unsafeMethod() { if (this == nullptr) { cout << "空指针不能访问成员变量" << endl; return; } std::cout << "Value: " << value << std::endl; } private: int value = 42; }; int main() { MyClass* ptr = nullptr; // 安全调用:空指针不访问成员变量 ptr->safeMethod(); // 可以正常运行 // 危险调用:访问成员变量 ptr->unsafeMethod(); // 运行时崩溃 return 0; }

七、常函数和常对象

1.常函数

        在成员函数后加const就变成了常量成员函数,即常函数。常函数的性质如下:

  • 常函数内不可以修改成员属性,也不可以调用常函数;

注:这样能够使常函数无法通过修改成员属性来修改自己内部的值,不会因此失去常量性。

  • 成员属性声明时加关键字mutble后,在常函数中可以修改;
  • 如果两个函数名字相同、参数相同,但是一个是常函数,一个是非常函数,那么这两个函数属于函数重载(函数重载的特殊情况),非常对象优先调用非常函数,常对象只能调用常函数。

        (1)下面的代码报错,是因为常函数内不能修改成员属性。

#include<iostream> using namespace std; class People { public: int num; People(int num){ this->num = num; } void S() const { num = 1; } }; int main() { return 0; }

        解决方法是在成员属性处声明mutable:

#include<iostream> using namespace std; class People { public: mutable int num; People(int num){ this->num = num; } void S() const { num = 1; } }; int main() { return 0; }

        (2)下面的代码报错,是因为常函数中只能调用常函数。

#include<iostream> using namespace std; class People { public: mutable int num; People(int num){ this->num = num; } void Q() { } void S() const { this->Q(); } }; int main() { return 0; }

2.常对象

        声明对象前加const称该对象为常量成员对象,即常对象。常对象的性质如下:

  • 常对象只能调用常函数。

        下面的代码报错,是因为常对象只能调用常函数,而非常对象既能调用常函数也能调用非常函数,非常对象优先调用非常函数。

#include<iostream> using namespace std; class People { public: mutable int num; People(){} People(int num){ this->num = num; } void S() { } }; int main() { const People p; p.S(); return 0; }

        解决方法是将People类中的S()函数设置为常函数,或者将对象p设置为非常对象。

Could not load content