数据共享和成员特性
类的重要特性是使数据封装和隐藏,但同时也给数据共享及外部访问带来了不便.为此,C++提供了静态成员和友元机制来解决这些问题
静态成员
静态成员是类中的成员,是类的一部分,在类外不能访问,从而起保护作用
静态成员有静态数据成员和静态成员函数之分
静态数据成员:与静态变量相似,具有静态生存期,是在类中声明的全局数据成员,能被同一个类的所有对象共享
公有静态成员函数:可通过类对象来访问,也可通过”类名::静态成员函数“的形式在程序中直接调用
1.静态数据成员
使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对于多个对象来说,静态数据成员值存储一处,供所有对象共享
定义:
在类中使用关键字static声明静态数据成员.注意:在类中声明仅仅是说明静态数据成员是类中的成员的这个关系,即便是用该类定义对象,该静态数据成员也不会分配内存空间
在类外为静态数据成员分配内存空间并初始化.类中的数据成员的内存空间是在对象定义时分配的,而静态数据成员的内存空间为所有该类对象所共享,只能分配一次,因此必须在类的外部作实际定义才能为所有对象共享.
格式:
<数据类型><类名>::<静态数据成员名> = <值>
#include <iostream>
using namespace std;
class CSum
{
public:
CSum(int a=0,int b=0)
{
nSum += a+b;
}
int getSum()
{
return nSum;
}
void setSum(int sum)
{
nSum = sum;
}
private:
static int nSum; // 声明静态数据成员
};
int CSum::nSum = 0; // 静态数据成员的实际定义和初始化
int main()
{
CSum one(10,2),two;
cout << "one: sum = " << one.getSum() << endl;
cout << "two: sum = " << two.getSum() << endl;
two.setSum(5);
cout << "one: sum = " << one.getSum() << endl;
cout << "two: sum = " << two.getSum() << endl;
return 0;
}
/**output:
* one:sum = 12
* two:sum = 12
* one:sum = 5
* two:sum = 5
*/
注意:
静态数据成员在类中所作的声明仅仅声明该成员是属于哪个类的,它是形式上的虚的成员,还必须在类的外部作实际定义才能被所有对象共享.因此,**静态数据成员实际定义和初始化本身不受public,private和protected等访问属性的限制
静态数据成员具有静态生存期,即从实际定义时开始产生,到程序结束时消失*.注意*,静态数据成员的内存空间同样不能在类的构造函数中创建或析构函数中释放.
静态数据成员的访问属性可以是public,protected,private.当静态数据成员为public时,则在类外对该成员的访问和引用可有两种方式:
通过对象来引用
直接引用:
<类名>::<静态成员名>
#include <iostream>
using namespace std;
class CSum
{
public:
static int nSum; // 声明公有型静态数据成员
};
int CSum::nSum = 0; // 静态数据成员的实际定义和初始化
int main()
{
CSum one;
one.nSum = 10; //通过对象来引用
//...
CSum::nSum = 12; // 直接引用
cout << one.nSum << endl; // 输出12
return 0;
}
2.静态成员函数
- 静态成员函数也是所有对象所共享的成员.因此,对于public静态成员函数,除了可用对象来引用外,还可通过”类名::成员”直接来引用
- 在类中,静态数据成员可以被成员函数引用,也可以被静态成员函数所引用.但是反过来,静态成员函数 却不能直接引用类中说明的 非静态成员.因为
- 没创建对象:类中的非静态成员不存在
- 创建对象:静态成员函数无法确定函数中所引用的非静态成员属于哪个对象
// 使用静态成员来实现数据插入,输出和排序操作
#include <iostream>
using namespace std;
class CData
{
public:
static void Add(int a) // 添加数据a
{
if(pCur >= data +20)
{
cout << "内存空间不够,无法添加!" << endl;
}
else
{
*pCur = a;
pCur++;
}
}
static void Print(void);
static void Sort(void);
private:
static int data[20]; // 声明静态内存空间
static int *pCur; // 声明静态指针成员
};
int CData::data[20]; // 实际定义,默认的初值为0
int *CData::pCur = data; // 实际定义,设初值为data数组的首地址
void CData::Print(void)
{
for(int i = 0;i<(pCur-data);i++)
{
cout << data[i] <<",";
}
cout << endl;
}
void CData::Sort(void)
{
int n = pCur - data;
for(int i = 0;i<n-1;i++)
{
for(int j=i+1;j<n;j++)
{
if(data[i] > data[j])
{
int temp = data[i];
data[i] = data[j];
data[j] = temp;
}
}
}
}
int main()
{
CData::Add(20);
CData::Add(40);
CData::Add(-50);
CData::Add(7);
CData::Add(13);
CData::Print();
CData::Sort();
CData::Print();
return 0;
}
/**
* 20,40,-50,7,13,
* -50,7,13,20,40
* /
友元
为了在类外只对某个对象的数据成员进行操作,于是, 友元(friend) 机制便产生了
1.友元概述
- 类的private和protected数据成员只能在类中由该类的成员函数来访问
- 类的对象及外部函数只能访问类的public成员函数
- 如果在类中用friend关键字声明一个函数,且该函数的形参中还有该类的对象形参,这个函数便可以通过形参对象或通过在函数体中定义该类对象来访问该类的任何私有和保护型函数.用friend声明的这个函数就称为这个类的友元函数.
- 友元还可以是类,即一个类可以作为另一个类的友元,称为友元类.例如:B类作为A类的友元时,就意味着要在B类中通过A类对象来访问A类中的所有成员
- 采用友元机制,通过类对象可以访问或引用类中的所有成员.这样,通过友元机制修改数据成员,修改的也是某个对象的数据成员.
2.友元函数
- 友元函数分为友元外部函数和友元成员函数
- 友元成员函数:当一个函数f是A类的友元,且f还是另一个类B的成员函数时,则这样的友元称为友元成员函数
- 友元外部函数:若f不属于任何类的成员,则这样的友元称为友元外部函数.通常将友元外部函数直接简称为友元函数
- 友元函数格式:
friend <函数类型> <函数名>(形参表)
{...}
说明:
- 友元函数的定义可以在类中进行,也可将友元函数在类中声明,而将其实现在类外定义.但在类外定义时,不能像成员函数那样指明它所属的类
- 由于友元函数是一个外部函数,所以它对类中成员的访问只能通过类对象来进行,而不能直接去访问.这里的对象可以通过形参来指定,也可在友元函数中进行定义
- 由于友元函数是在类中声明的外部函数,因此它的声明可以出现在类的任何地方(public,private,protected),一般放在类体的开头或最后
- 由于友元函数不是类的成员,因此它在调用时不能指定其所属的类,更不能通过对象来引用友元函数.
- 友元函数采用形参对象的方式对类中的数据操作,通过对象的”引用”传递,达到修改对象数据的目的
#include <iostream> using namespace std; class CPoint { friend CPoint Inflate(CPoint &pt,int nOffset); // 声明一个友元函数 public: CPoint(int x=0,int y=0) { xPos = x; yPos = y; } void Print() { cout << "Point(" << xPos << "," << yPos << ")" << endl; } private: int xPos,yPos; }; CPoint Inflate(CPoint &pt,int nOffset) // 友元函数的定义 { pt.xPos += nOffset; // 直接改变私有数据成员 pt.yPos += nOffset; return pt; } int main() { CPoint pt(10,20); pt.Print(); Inflate(pt,3); // 直接调用友元函数 pt.Print(); return 0; } /** * Point(10,20) * Point(13,23) */
3.友元成员函数
- 格式:
friend <函数类型> <类名>::<函数名>(形参表)
{...}
- 友元成员函数还是另一个类的成员函数,这里的类名指的是它作为成员所在的类名
- 友元成员函数的定义既可在类中进行,也可将友元成员函数在类中声明,而将其实现放在类外定义.但当在类外定义时,应指明它所属的类
#include <iostream>
using namespace std;
class CRect; // 声明类名CRect,以便可以被后面引用
class CPoint
{
public:
void Inflate(CRect &rc,int nOffset); // 成员函数
CPoint(int x=0,int y=0)
{
xPos = x;
yPos = y;
}
void Print()
{
cout << "Point(" << xPos << "," << yPos << ")" << endl;
}
private:
int xPos,yPos;
};
class CRect
{
friend void CPoint::Inflate(CRect &rc, int nOffset);
public:
CRect(int x1 = 0,int y1 = 0,int x2 = 0,int y2 = 0)
{
xLeft = x1;
xRight = x2;
yTop = y1;
yBottom = y2;
}
void Print()
{
cout << "Rect(" << xLeft << "," << yTop << "," << xRight << "," << yBottom << ")" << endl;
}
private:
int xLeft,yTop,xRight,yBottom;
};
void CPoint::Inflate(CRect &rc,int nOffset) // 友元函数的定义
{
xPos += nOffset; // 直接改变自己类中的私有数据成员
yPos += nOffset;
// 访问CRect类的私有成员
rc.xLeft += nOffset;
rc.xRight += nOffset;
rc.yTop += nOffset;
rc.yBottom += nOffset;
}
int main()
{
CPoint pt(10,20);
CRect rc(0,0,100,80);
pt.Print();
rc.Print();
pt.Inflate(rc,3); // 直接调用友元函数
pt.Print();
rc.Print();
return 0;
}
/**
* Point(10,20)
* Rect(0,0,100,80)
* Point(13,23)
* Rect(3,3,103,83)
*/
4.友元类
- 将一个类声明为另一个类的的友元,称为友元类
- 当一个类作为另一个类的友元时,就意味着这个类的所有成员函数都是另一个类的友元成员函数.
- 格式:
friend class <类名>;
#include <iostream>
using namespace std;
class CPoint
{
friend class COther; //声明友元类
public:
CPoint(int x=0,int y=0)
{
xPos = x;
yPos = y;
}
void Print()
{
cout << "Point(" << xPos << "," << yPos << ")" << endl;
}
private:
int xPos,yPos;
void Inflate(int nOffset)
{
xPos += nOffset;
yPos += nOffset; // 直接改变自己类中的私有数据成员
}
};
class COther
{
public:
COther(int a = 0 ,int b = 0)
{
pt.xPos = a;
pt.yPos = b; // 通过对象访问类CPoint的私有数据成员
}
void Display(void)
{
pt.Inflate(10); // 通过对象访问类CPoint的私有成员函数
pt.Print();
}
private:
CPoint pt;
};
int main()
{
COther one(12,18);
one.Display();
return 0;
}
/**
* Point(22,28)
*/
- 说明:
- 友元关系反映了程序中类与类之间,外部函数之间,成员函数之间和另一个类等之间的关系,这个关系是单向的.即当在CPoint中声明COther是CPoint的友元时,只能在COther类中通过CPoint对象访问CPoint类的所有成员,而在CPoint类中无法访问COther类的私有和保护型成员.
- 友元只能通过对象来访问声明友元所在类的成员
常类型
- 常类型:使用修饰符
const
说明的类型.常类型的变量或对象的值是不能被更新的.因此,定义或说明常类型时必须进行初始化
1.常对象
- 格式:
<类名> const <对象名>
定义常对象时,修饰符const可以放在类名后面,也可以放在类名前面
2.常指针和常引用
常指针也是可以使用const修饰的,但是,const的位置不同,其含义也不同
形式:三种
1.常量指针:将const
放在指针变量类型之前,表示声明一个指向常量的指针.此时,程序中不能通过指针来改变它所指向的数据值,但可以改变指针本身的值
int a = 1,b = 2;
const int *p1 = &a; // 声明一个指向int常量的指针,指针的地址为a的地址
*p1 = 2; // 错误,不能更改指针所指向的数据值
p1 = &b; // 正确,指向常量的指针本身的值是可以改变的
说明:在这种形式的定义的常量指针,在声明时可以赋初值,也可以不赋初值
2.指针常量(或常指针):将const
放在指针定义语句的指针名之前,表示指针本身是一个常量.因此,不能改变这种指针变量的值,但可以改变指针变量所指向的数据值.
int a = 1,b = 2;
int * const p1 = &a; // 声明指向int类型的指针常量p1,指针地址为a的地址
int * const p2; // 错误,在声明指针常量时,必须初始化
*p1 = 2; // 正确,指针所指向的数据值可以改变
p1 = &b; // 错误,指针常量本身的值是不可改变的
3.将const在上述两个地方都加,表示声明一个指向常量的指针常量,指针本身的值不可改变,而且它所指向的数据的值也不能通过指针改变
int a = 1,b = 2;
const int * const pp = &a;
*pp =2; // 错误
pp = &b;// 错误
说明:第2和第3种形式定义的指针常量,在声明时必须赋初值
- 使用const声明引用,称为常引用:该引用所引用的对象不能被更新.
- 常引用定义格式:
const <类型说明符> & <引用名>
- 在实际应用中,常指针和常引用往往用来当做函数的形参,这样的参数称为常参数.使用常参数表示该函数不会更新某个参数所指向或所引用的对象,这样,在参数传递的过程中就不需要执行拷贝构造参数,这将会改善程序的运行效率.
// 具有 常参数 的函数传递
#include <iostream>
using namespace std;
class COne
{
public:
void print(const int *p,int n) //使用常参数
{
cout << "{" << *p;
for(int i = 1;i < n;i++)
{
cout << "," <<*(p+i);
}
cout << "}" << endl;
}
};
int main()
{
int array[6] = {1,2,3,4,5,6};
COne one;
one.print(array,6);
return 0;
}
/**
* {1,2,3,4,5,6}
*/
常成员函数
- 常成员函数:使用
const
关键字进行声明的成员函数.只有常成员函数才有资格操作常量或常对象. - 格式:
<类型说明符> <函数名>(参数表) const
其中,const
是加在函数说明后面的类型说明符,他是函数类型的一个组成部分,因此,在函数实现部分也要带const
关键字
// 常成员函数的使用
#include <iostream>
using namespace std;
class COne
{
public:
COne(int a,int b)
{
x = a;
y = b;
}
void print();
void print() const; // 声明常成员函数
private:
int x,y;
};
void COne::print()
{
cout << x << "," << y << endl;
}
void COne::print() const
{
cout << "使用常成员函数: " << x << "," << y << endl;
}
int main()
{
COne one(5,4);
one.print();
const COne two(20,52);
two.print();
return 0;
}
/**
* 5,4
* 使用常成员函数: 20,52
*/
4.常数据成员
const
也可说明数据成员.由于const
类型对象必须被初始化,并且不能更新,因此,当类中声明const
数据成员时,只能通过成员初始化列表的方式来生成构造函数对数据成员进行初始化.
#include <iostream>
using namespace std;
class COne
{
public:
COne(int a)
:x(a),r(x) // 常数据成员的初始化
{}
void print();
const int &r; // 引用类型的常数据成员
private:
const int x; // 常数据成员
static const int y; // 静态常数据成员
};
const int COne::y = 10; // 静态常数据成员的初始化
void COne::print()
{
cout << "x = " << x << ",y = " << y << ",r = " << r << endl;
}
int main()
{
COne one(100);
one.print();
return 0;
}
/**
* x = 100,y = 10,r = 100
*/
this指针
- this指针:一个仅能被类的非静态成员函数所访问的特殊指针.
- 当一个对象调用成员函数时,编译器先将对象的地址赋给this指针,然后调用成员函数
one.copy(two);// 实际上是 copy(&one,two);只不过&one参数被隐藏了
- 通过*this可以判断是哪个对象来调用该成员函数或重新指定对象.
// this指针的使用
#include <iostream>
using namespace std;
class COne
{
public:
COne()
{
x = y = 0;
}
COne(int a,int b)
{
x = a;
y = b;
}
void copy(COne &a); // 对象引用作函数参数
void print()
{
cout << x << "," << y << endl;
}
private:
int x,y;
};
void COne::copy(COne &a)
{
if(this == &a) // 这个this是操作该成员函数的对象的地址,即main()函数中的one
return;
*this = a; // 将形参a(对象的引用)赋值给操作该成员函数的对象
}
int main()
{
COne one,two(3,4);
one.print();
one.copy(two);
one.print(); // 实际上是 two.print()
return 0;
}
/**
* 0,0
* 3,4
*/
- 若成员函数的形参名与该类的成员变量名同名,则必须用this指针来显式区分
class CPoint
{
public:
CPoint(int x=0,int y=0)
{
this->x = x;
this->y = y;
}
void Offset(int x,int y)
{
(*this).x += x;
(*this).y += y;
}
void Print() const
{
cout << "Point(" << x << "," << y << ")" << endl;
}
private:
int x,y;
};
- 注意:静态成员函数不能使用this指针,因为它是为所有对象所共享,this指针无法确定this的具体指向