数据与函数
- 数据与函数
- 局部变量
- 全局变量
- 类的数据成员
- 类的静态成员
- 静态数据成员
- 静态函数成员
- 友元
- 友元函数
- 友元类
== 函数之间实现数据共享有以下几种方式:局部变量、全局变量、类的数据成员、类的静态成员和友元。==
如何共享局部变量呢?
- 在主调函数和被调函数之间通过参数传递来共享。
- 全局变量具有文件作用域,所以作用域中的各个函数都能共享全局变量。
- 类的数据成员具有类作用域,能够被类的函数成员共享。
- 类的静态成员。
- 友元共享。
数据与函数
在结构化程序设计中,有人提出“数据结构+算法=程序设计”,数据结构就是数据的组织,算法是用函数实现的,可见数据和函数很早就被看作程序设计的重点了。面向对象程序设计中,这种观点应稍作一下修改:“数据结构+算法=对象”。就是数据和函数构成了类的对象。
面向对象程序设计中,数据用来描述对象的属性,函数是行为,用来处理数据。将数据和函数封装到一个类里,类中的函数成员可以访问数据成员,函数成员之间可以实现数据共享。
局部变量
局部变量其实一般就是说具有块作用域的变量。如果要在不同的块之间共享存储在局部变量中的数据,只能通过参数传递来实现。这种共享只能在主调函数和被调函数之间进行。因为局部变量具有块作用域,所以不同函数中的局部变量是互不可见的,这也是函数之间的一种数据隐藏,在结构化程序设计中这是实现数据隐藏的唯一方式。而在面向对象设计中主要靠封装来隐藏数据。
全局变量
全局变量具有文件作用域,在整个作用域中,除了定义了同名局部变量的块以外的地方都可以直接访问全局变量。不同的函数在不同的地方都能访问相同的全局变量,这样就实现了函数之间对全局变量的共享。
使用全局变量的优缺点:
- 优点:在进行软件开发的时候使用全局变量会很方便,不用那么多的传递参数。
- 缺点:在多处使用了全局变量以后就不清楚此变量在哪些函数中使用过了,除非逐个查找,这样就会带来一个问题,如果全局变量要做一些大的改动,我们就不知道它会影响到哪些函数,这时就要通过大量的测试来排除全局变量变动带来的问题。
结构化程序设计中,只能使用前面提到的局部变量参数传递和全局变量这两种方式共享数据。这两种方式各有利弊,局部变量隐藏性好,但是传递的时候会有很大的系统开销,所以一般只传递少量数据,大量数据通过全局变量的方式共享,但这又使得程序模块间耦合严重,扩展和维护都很困难。C++中可以通过具有文件作用域的全局对象来实现数据和函数的共享,这实际上也属于全局变量的范畴。
类的数据成员
类中封装了数据成员和函数成员。其中数据成员可以被同一个类中的所有函数成员访问,这样就实现了数据的共享。但是我们又可以通过设置访问控制权限,把这种共享限制到类的范围之内,这样类的数据成员对外界来说是隐藏的。也就达到了共享与隐藏的辩证结合。总起来说,要达成这样的效果需要把类的数据成员都设置为私有类型。
- 若将类的数据参数设置为私有,则外部对象不能直接访问该数据对象,只能通过函数接口来访问该私有变量。这样程序模块间的耦合性就大大降低,提高了程序的可扩展性和可维护性,能够大大提高软件开发效率。
类的静态成员
静态成员包括静态数据成员和静态函数成员。
静态数据成员
- 使用场景:
之前讲到的类的数据成员都是一个对象一个拷贝,每个对象都有自己的数据成员的值,但有时候我们需要让某个类的所有对象共享某个数据成员,比如我们有一个学生类CStudent,其中有学号、姓名等数据成员,如果我们想要对学生人数进行统计,那么这个数据应该放在哪里呢?放在类外的某个地方的话数据的隐藏性就不好了。可能有朋友就说了,可以在类中增加一个数据成员存放学生人数,但这样就有两个坏处,第一,该类的每个对象都将有一个这个数据成员的副本,浪费内存;第二,学生人数可能在不同的对象中有不同的数,不一致了。这个时候就需要静态数据成员了。 - 特性
静态数据成员存放的是类的所有对象的某个共同特征的数据,对于每个对象而言,该数据都是相同的,在内存中只存在一份。这与类的一般数据成员不同,一般数据成员会在每个对象中都有一个拷贝,每个拷贝的值可能都不一样。静态数据成员由类的所有对象共同维护和使用,这样就实现了类的对象间的数据共享。 - 使用
声明静态数据成员的方式与一般数据成员不同的是,其前面要加static关键字,比如static int x;。静态数据成员具有静态生存期。它可以通过类名或对象名访问。用类名直接访问的方式为:类名::标识符。在类的声明中仅对静态数据成员进行引用性说明,就是带static的声明,必须在其文件作用域的其他地方进行定义性说明,也可以进行初始化,如果不显式初始化的话会将此静态数据成员初始化为0。
#include <iostream>
using namespace std;
class CStudent // 学生类的声明
{
public: CStudent(int nID) { m_nID = nID; m_nCount++; } // 构造函数CStudent(CStudent &s); // 拷贝构造函数int GetID(){ return m_nID;}void GetCount() { cout<<" 学生人数:"<<m_nCount<<endl; } // 输出静态数据成员
private: int m_nID;static int m_nCount; // 静态数据成员的引用性说明
};
CStudent::CStudent(CStudent &s)
{ m_nID = s.m_nID;m_nCount ++;
}int CStudent::m_nCount=0; // 静态数据成员的定义性说明及初始化
int main()
{CStudent A(6); // 定义对象Acout<<"学生A,"<<A.GetID();A.GetCount(); // 输出此时学生个数CStudent B(A); // 定义对象B,并用A初始化Bcout<<"学生B,"<<B.GetID(); // 输出此时学生个数B.GetCount();return 0;
}
- 若将m_nCount的属性改为public,将可以通过CStudent::m_nCount直接访问。
- 类的使用中一般情况,将数据定义为私有。
静态函数成员
类的私有静态变量无法用限位符来直接调用,此时就采用静态函数来解决。
静态成员函数跟静态数据成员一样,也是由类的所有对象所共有,由他们共同维护和使用。声明时前面也要加static关键字,比如,static void fun(void);。我们可以通过类名或对象名调用公有类型的静态成员函数,而非静态成员函数只能由对象名调用。
静态成员函数可以访问该类的静态数据成员和其他静态成员函数,如果要访问非静态数据成员,则必须将对象作为参数传递进去,然后通过传进去的对象名来访问。
class A
{
public:static void f(A a);
private:int x;
};
void A::f(A a)
{cout<<x; //对x的引用是错误的cout<<a.x; //正确
}
- 所以静态成员函数访问非静态成员很麻烦,它一般用来访问全局变量和同一个类中的静态数据成员。
#include <iostream>
using namespace std;
class CStudent // 学生类的声明
{
public: CStudent(int nID) { m_nID = nID; m_nCount++; } // 构造函数CStudent(CStudent &s); // 拷贝构造函数int GetID() { return m_nID;}static void GetCount() { cout<<" 学生人数:"<<m_nCount<<endl; } // 输出静态数据成员
private: int m_nID;static int m_nCount; // 静态数据成员的引用性说明
};
CStudent::CStudent(CStudent &s)
{ m_nID = s.m_nID;m_nCount ++;
}int CStudent::m_nCount=0; // 静态数据成员的定义性说明及初始化
int main()
{CStudent A(6); // 定义对象Acout<<"学生A,"<<A.GetID();A.GetCount(); // 输出此时学生个数CStudent B(A); // 定义对象B,并用A初始化Bcout<<"学生B,"<<B.GetID(); // 输出此时学生个数CStudent::GetCount(); //直接通过类名和限定符来访问静态函数return 0;
}
友元
友元是一种破坏类的封装性获取类的隐藏数据的方式。
友元机制:B类的成员函数直接访问A的数据成员。
class A
{
public:int Getx() { return x; }
private:int x;
};
class B
{
public:void Set(int y);
private:A a;
};//不正确用法
void B::Set(int y)
{a.x = y;
}
通过友元的方式,某个普通函数或者类的成员函数可以访问某个类中的私有数据,这样就等于在类的封装的很好的外衣上剪开了一个小洞,外界可以通过这个小洞访问到一些类内部的数据。友元提供了一种不同类或对象的成员函数之间、类的成员函数与普通函数之间共享数据的机制。它破坏了类的封装性和类数据的隐藏性,但是又给我们进行软件开发提供了很多方便,在我们实地进行软件开发的时候可以自己在共享和封装之间平衡一下,决定是否选择使用友元。原则上尽量少使用或不使用友元,除非确实很大的提高了开发效率。
- 在一个类中声明友元的方式是,用关键字friend把普通函数、其他类的成员函数或其他类声明为此类的友元,用friend声明的元素就可以访问此类的私有数据。如果友元是普通函数或是类的成员函数就叫做友元函数,如果友元是一个类则叫做友元类,友元类的所有成员函数均为友元函数。
- 友元的声明位置为想使用的资源的范围内。
友元函数
友元函数是在类的声明中用关键字friend修饰的普通函数或者其他类的成员函数。友元函数虽不是本类的成员函数,但在它的函数体中可以访问该类对象的私有成员和保护成员。
#include <iostream>
#include <math.h>
using namespace std;
class Data //Data类声明
{
public: //外部接口Data(int xx=0) { x=xx; }int GetX() { return x; }friend int Add(Data &a, Data &b);
private: //私有数据成员int x;
};
int Add(Data &a, Data &b)
{return a.x + b.x;
}
int main()
{Data a(1);Data b(2);int sum = Add(a, b);cout<<"The sum is "<<sum<<endl;return 0;
}
- 在Data类中声明友元函数Add时只给出了友元函数原型,友元函数Add的实现在类Data外。我们看到,在Add函数体中直接通过对象名访问了Data类的私有成员x,这就是友元的强大作用。我们在类外用一般方式访问x的话必须通过公共接口GetX来实现,若要访问的私有成员很多或者要访问的地方很多就要多次调用函数,对于我们写代码和程序运行都有效率上的损失,但是用友元也有个很大的缺点就是,如果私有数据x发生结构性的变化,那么友元函数就要做相应的修改,所有调用友元函数的地方可能也要修改,这就降低了开发效率,所以是否要使用友元可以自己权衡。
友元类
类也可以声明为另一个类的友元,就像友元函数那样,这个作为另一个类的友元的类就叫做友元类。如果一个类B是类A的友元类,则类B的所有成员函数都是类A的友元函数,都能访问类A的私有成员和保护成员。
class A
{...friend class B; // 将类B声明为类A的友元类...
}
class A
{
public:void Display() { cout<<x<<endl;}friend class B;
private:int x;
}
class B
{
public:void Set(int i);void Display();
private:A a;
};
void B::Set(int i)
{a.x=i; // 因为类B是类A的友元类,所以类B的成员函数可以访问类A对象的私有成员
}
void B::Display()
{a.Display();
}
友元类特性:
- ***友元关系不能传递,***如果类B是类A的友元,类C又是类B的友元,类C和类A如果没有声明则没有友元关系。
- 友元关系是单向的,如果类B是类A的友元,类B的成员函数可以访问类A对象的私有成员和保护成员,但是类A的成员函数不能访问类B对象的私有成员和保护成员。