C++类的静态成员详细讲解

简介: 在C++中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。

在C++中,静态成员是属于整个类的而不是某个对象,静态成员变量只存储一份供所有对象共用。所以在所有对象中都可以共享它。使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存。

静态成员的定义或声明要加个关键static。静态成员可以通过双冒号来使用即<类名>::<静态成员名>。

 

在C++中类的静态成员变量和静态成员函数是个容易出错的地方,本文先通过几个例子来总结静态成员变量和成员函数使用规则,再给出一个实例来加深印象。希望阅读本文可以使读者对类的静态成员变量和成员函数有更为深刻的认识。

 

第一个例子,通过类名调用静态成员函数和非静态成员函数

  1. class Point  
  2. {  
  3. public:   
  4.     void init()  
  5.     {    
  6.     }  
  7.     static void output()  
  8.     {  
  9.     }  
  10. };  
  11. void main()  
  12. {  
  13.     Point::init();  
  14.     Point::output();  
  15. }  

编译出错:error C2352: 'Point::init' : illegal call of non-static member function

结论1:不能通过类名来调用类的非静态成员函数。

 

第二个例子,通过类的对象调用静态成员函数和非静态成员函数

将上例的main()改为:

  1. void main()  
  2. {  
  3.     Point pt;  
  4.     pt.init();  
  5.     pt.output();  
  6. }  

编译通过。

结论2:类的对象可以使用静态成员函数和非静态成员函数。

 

第三个例子,在类的静态成员函数中使用类的非静态成员

  1. #include <stdio.h>  
  2. class Point  
  3. {  
  4. public:   
  5.     void init()  
  6.     {    
  7.     }  
  8.     static void output()  
  9.     {  
  10.         printf("%d\n", m_x);  
  11.     }  
  12. private:  
  13.     int m_x;  
  14. };  
  15. void main()  
  16. {  
  17.     Point pt;  
  18.     pt.output();  
  19. }  

编译出错:error C2597: illegal reference to data member 'Point::m_x' in a static member function

因为静态成员函数属于整个类,在类实例化对象之前就已经分配空间了,而类的非静态成员必须在类实例化对象后才有内存空间,所以这个调用就出错了,就好比没有声明一个变量却提前使用它一样。

结论3静态成员函数中不能引用非静态成员

 

第四个例子,在类的非静态成员函数中使用类的静态成员

  1. class Point  
  2. {  
  3. public:   
  4.     void init()  
  5.     {    
  6.         output();  
  7.     }  
  8.     static void output()  
  9.     {  
  10.     }  
  11. };  
  12. void main()  
  13. {  
  14.     Point pt;  
  15.     pt.output();  
  16. }  

编译通过。

结论4:类的非静态成员函数可以调用用静态成员函数,但反之不能。

 

第五个例子,使用类的静态成员变量

  1. #include <stdio.h>  
  2. class Point  
  3. {  
  4. public:   
  5.     Point()  
  6.     {    
  7.         m_nPointCount++;  
  8.     }  
  9.     ~Point()  
  10.     {  
  11.         m_nPointCount--;  
  12.     }  
  13.     static void output()  
  14.     {  
  15.         printf("%d\n", m_nPointCount);  
  16.     }  
  17. private:  
  18.     static int m_nPointCount;  
  19. };  
  20. void main()  
  21. {  
  22.     Point pt;  
  23.     pt.output();  
  24. }  

按Ctrl+F7编译无错误,按F7生成EXE程序时报链接错误

error LNK2001: unresolved external symbol "private: static int Point::m_nPointCount" (?m_nPointCount@Point@@0HA)

这是因为类的静态成员变量在使用前必须先初始化。

在main()函数前加上int Point::m_nPointCount = 0;

再编译链接无错误,运行程序将输出1。

结论5:类的静态成员变量必须先初始化再使用。

 

结合上面的五个例子,对类的静态成员变量和成员函数作个总结

一。静态成员函数中不能调用非静态成员。

二。非静态成员函数中可以调用静态成员。因为静态成员属于类本身,在类的对象产生之前就已经存在了,所以在非静态成员函数中是可以调用静态成员的。

三。静态成员变量使用前必须先初始化(如int MyClass::m_nNumber = 0;),否则会在linker时出错。

 

再给一个利用类的静态成员变量和函数的例子以加深理解,这个例子建立一个学生类,每个学生类的对象将组成一个双向链表,用一个静态成员变量记录这个双向链表的表头,一个静态成员函数输出这个双向链表。

  1. #include <stdio.h>  
  2. #include <string.h>  
  3. const int MAX_NAME_SIZE = 30;    
  4.   
  5. class Student    
  6. {    
  7. public:    
  8.     Student(char *pszName);  
  9.     ~Student();  
  10. public:  
  11.     static void PrintfAllStudents();  
  12. private:    
  13.     char    m_name[MAX_NAME_SIZE];    
  14.     Student *next;  
  15.     Student *prev;  
  16.     static Student *m_head;  
  17. };    
  18.   
  19. Student::Student(char *pszName)  
  20. {    
  21.     strcpy(this->m_name, pszName);  
  22.   
  23.     //建立双向链表,新数据从链表头部插入。  
  24.     this->next = m_head;  
  25.     this->prev = NULL;  
  26.     if (m_head != NULL)  
  27.         m_head->prev = this;  
  28.     m_head = this;    
  29. }    
  30.   
  31. Student::~Student ()//析构过程就是节点的脱离过程    
  32. {    
  33.     if (this == m_head) //该节点就是头节点。  
  34.     {  
  35.         m_head = this->next;  
  36.     }  
  37.     else  
  38.     {  
  39.         this->prev->next = this->next;  
  40.         this->next->prev = this->prev;  
  41.     }  
  42. }    
  43.   
  44. void Student::PrintfAllStudents()  
  45. {  
  46.     for (Student *p = m_head; p != NULL; p = p->next)  
  47.         printf("%s\n", p->m_name);  
  48. }  
  49.   
  50. Student* Student::m_head = NULL;    
  51.   
  52. void main()    
  53. {     
  54.     Student studentA("AAA");  
  55.     Student studentB("BBB");  
  56.     Student studentC("CCC");  
  57.     Student studentD("DDD");  
  58.     Student student("MoreWindows");  
  59.     Student::PrintfAllStudents();  
  60. }  

程序将输出:

当然在本例还可以增加个静态成员变量来表示链表中学生个数,如果读者有兴趣,就将这个作为小练习吧。

原文地址:http://blog.csdn.net/morewindows/article/details/6721430

img_e00999465d1c2c1b02df587a3ec9c13d.jpg
微信公众号: 猿人谷
如果您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】
如果您希望与我交流互动,欢迎关注微信公众号
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。

目录
相关文章
|
4天前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。
|
2天前
|
编译器 C语言 C++
|
2天前
|
编译器 C++
【C++】详解初始化列表,隐式类型转化,类静态成员,友元
【C++】详解初始化列表,隐式类型转化,类静态成员,友元
|
4天前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。
|
4天前
|
编译器 C++
【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 )
本文探讨了C++中类的成员函数,特别是取地址及const取地址操作符重载,通常无需重载,但展示了如何自定义以适应特定需求。接着讨论了构造函数的重要性,尤其是使用初始化列表来高效地初始化类的成员,包括对象成员、引用和const成员。初始化列表确保在对象创建时正确赋值,并遵循特定的执行顺序。
|
4天前
|
C语言 C++
【C++】日期类Date(详解)③
该文介绍了C++中直接相减法计算两个日期之间差值的方法,包括确定max和min、按年计算天数、日期矫正及计算差值。同时,文章讲解了const成员函数,用于不修改类成员的函数,并给出了`GetMonthDay`和`CheckDate`的const版本。此外,讨论了流插入和流提取的重载,需在类外部定义以符合内置类型输入输出习惯,并介绍了友元机制,允许非成员函数访问类的私有成员。全文旨在深化对运算符重载、const成员和流操作的理解。
|
4天前
|
定位技术 C语言 C++
C++】日期类Date(详解)①
这篇教程讲解了如何使用C++实现一个日期类`Date`,涵盖操作符重载、拷贝构造、赋值运算符及友元函数。类包含年、月、日私有成员,提供合法性检查、获取某月天数、日期加减运算、比较运算符等功能。示例代码包括`GetMonthDay`、`CheckDate`、构造函数、拷贝构造函数、赋值运算符和相关运算符重载的实现。
|
4天前
|
编译器 C++
【C++】类和对象③(类的默认成员函数:赋值运算符重载)
在C++中,运算符重载允许为用户定义的类型扩展运算符功能,但不能创建新运算符如`operator@`。重载的运算符必须至少有一个类类型参数,且不能改变内置类型运算符的含义。`.*::sizeof?`不可重载。赋值运算符`=`通常作为成员函数重载,确保封装性,如`Date`类的`operator==`。赋值运算符应返回引用并检查自我赋值。当未显式重载时,编译器提供默认实现,但这可能不足以处理资源管理。拷贝构造和赋值运算符在对象复制中有不同用途,需根据类需求定制实现。正确实现它们对避免数据错误和内存问题至关重要。接下来将探讨更多操作符重载和默认成员函数。
|
4天前
|
存储 编译器 C++
【C++】类和对象③(类的默认成员函数:拷贝构造函数)
本文探讨了C++中拷贝构造函数和赋值运算符重载的重要性。拷贝构造函数用于创建与已有对象相同的新对象,尤其在类涉及资源管理时需谨慎处理,以防止浅拷贝导致的问题。默认拷贝构造函数进行字节级复制,可能导致资源重复释放。例子展示了未正确实现拷贝构造函数时可能导致的无限递归。此外,文章提到了拷贝构造函数的常见应用场景,如函数参数、返回值和对象初始化,并指出类对象在赋值或作为函数参数时会隐式调用拷贝构造。
|
4天前
|
存储 编译器 C语言
【C++】类和对象②(类的默认成员函数:构造函数 | 析构函数)
C++类的六大默认成员函数包括构造函数、析构函数、拷贝构造、赋值运算符、取地址重载及const取址。构造函数用于对象初始化,无返回值,名称与类名相同,可重载。若未定义,编译器提供默认无参构造。析构函数负责对象销毁,名字前加`~`,无参数无返回,自动调用以释放资源。一个类只有一个析构函数。两者确保对象生命周期中正确初始化和清理。