一、对象成员的初始化
一个类中说明具有某个类的类型的数据成员,这些成员称为对象成员。对象成员的构造函数调用顺序取决于这些对象成员在类中的说明顺序,跟它们在成员初始化列表中给出的顺序无关。如下示例:
class Num{
private:
int num1;
public:
Num():num1(0){
cout << "使用Num类的无参构造方法进行对象初始化" << "\n";
}
Num(int a):num1(a){
cout << "使用Num类的有参构造方法进行对象初始化" << "\n";
}
~Num(){
cout << "销毁创建的Num类对象" << "\n";
}
};
class Math{
private:
Num num10;
Num num20;
int num2;
public:
Math():num2(0){
cout << "使用Math类的无参构造方法进行对象初始化" << "\n";
}
Math(int i,int j,int k):num20(i),num10(j){//与参数顺序无关,与类中声明的顺序有关
this->num2 = k;//普通对象可以使用这种方式初始化,但const和引用对象只能使用参数列表形式进行初始化
cout << "使用Math类的有参构造方法进行对象初始化" << "\n";
}
~Math(){
cout << "销毁创建的Math类对象" << "\n";
}
};
void example2();
int main(){
example2();
return 0;
}
void example2(){
Math m(5,6,7);
cout << "成功创建Math类的对象" << endl;
/**
* 使用Num类的有参构造方法进行对象初始化
* 使用Num类的有参构造方法进行对象初始化
* 使用Math类的有参构造方法进行对象初始化
* 成功创建Math类的对象
* 销毁创建的Math类对象
* 销毁创建的Num类对象
* 销毁创建的Num类对象
*/
}
二、静态成员
如果类的数据成员或成员函数使用关键字static进行修饰,这样的成员称为静态数据成员或静态成员函数,统称为静态成员。
1、静态成员
#include<iostream>
using std::cout;
class StaticMember{
private:
int y;
public:
static int x;
StaticMember(){}
StaticMember(int a,int b){//静态成员不能使用参数列表进行初始化,而与之相反的const成员和引用成员必须使用参数列表进行初始化
x = a;
y = b;
}
static int getX(){//静态成员函数
return x;
}
static void setY(StaticMember &sm,int a){//静态成员函数
sm.y = a;
}
int getY(){return y;}//非静态成员函数
};
int StaticMember::x = 25;//静态数据成员初始化
void example3();
int main(){
example3();
return 0;
}
void example3(){
cout << StaticMember::x << endl;//通过类成员限定符来访问静态数据成员 25
StaticMember a,b;//初始化两个对象
a.setY(a,30);//设置对象a的数据成员y
cout << "y=" << a.getY() << ",x=" << a.getX() << ",x=" << b.getX() << endl;//y=30,x=25,x=25
StaticMember c(50,100);
cout << "y=" << a.getY() << ",x=" << a.getX() << ",x=" << b.getX() << endl;//y=30,x=50,x=50
}
静态成员与一般成员有以下不同:
① 可以不指向某个具体的类,只与类名连用;
② 在没有建立对象之前,静态成员就已经存在;
③ 静态成员是类的成员,不是对象的成员;
④ 静态成员为该类的所有对象共享,它们被存储于一个公共内存中;
⑤ 静态成员没有this指针,所以除非显式的把指针传给它们,否则不能存取类的数据成员;
⑥ 静态成员函数不能被说明为虚函数;
⑦ 静态成员函数不能直接访问非静态成员。
2、静态对象
#include<iostream>
using std::cout;
class StaticObject{
private:
int x;
public:
StaticObject(int a){
x = a;
cout << "对象初始化,x=" << x << "\n";
}
~StaticObject(){
cout << "销毁对象" << "\n";
}
void setX(){
++x;
}
int getX(){
return x;
}
};
void example4();
int main(){
example4();
return 0;
}
void example4(){
for(int i=0;i<3;i++){
static StaticObject a(10);
StaticObject b(10);
a.setX();
b.setX();
cout << "a.x=" << a.getX() << ",b.x=" << b.getX() << endl;
}
// 对象初始化,x=10 静态对象a的构造函数只调用一次,它是在b对象还没初始化之前就已经调用的
// 对象初始化,x=10
// a.x=11,b.x=11
// 销毁对象 //这里销毁的是对象b,因为它是for循环语句中的局部对象,其生命周期只能与本次循环共存,每次结束循环都要调用一次析构函数
// 对象初始化,x=10
// a.x=12,b.x=11
// 销毁对象
// 对象初始化,x=10
// a.x=13,b.x=11
// 销毁对象
// 销毁对象 //而静态对象a的析构函数是在程序结束之前才调用的,而且只调用一次
}
三、友元 函数
有时两个概念上相近的类要求其中一个类可以元限制的存取另一个类的成员,这时可以使用友元函数,友元函数可以存取私有成员、公有成员和保护成员。
1、类本身的友元函数
先看一下示例,如下:
class Rectangle {
private:
int length, width;
public:
Rectangle(int a, int b) {
length = a;
width = b;
}
int getLength() {
return length;
}
int getWidth() {
return width;
}
friend int areaDiff(Rectangle&, Rectangle&); //声明友元函数
};
int areaDiff(Rectangle &r1, Rectangle &r2) { //定义友元函数
int area1 = r1.length * r1.width;
int area2 = r2.length * r2.width;
return area1 - area2;
}
void example5();
int main(){
example5();
return 0;
}
void example5(){
Rectangle a(10,5),b(5,4);
cout << "面积之差是:" << areaDiff(a,b) << endl;//面积之差是:30
}
areaDiff是为Rectangle类声明的一个友元函数,虽然在类中说明它,但它并不是类的成员函数,所以可以在类外面像普通函数那样定义这个函数。由于友元不是类的成员,所以它没有this指针,在访问该类对象的成员时,必须使用对象名,而不能直接使用类的成员名。
友元函数是类中说明的,友元属于类接口的一部分,但函数的定义一般在类体外。友元函数可以直接访问对象的私有成员,可以省去调用类成员函数的开销,但这也破坏了封装和数据隐藏,导致程序的可维护性变差。
注意:友元函数也可以在类中声明时定义,如果在类外定义,不能再使用friend关键字。
2、将成员函数用做友元
一个类的成员函数(包括构造函数和析构函数)也可以通过使用friend说明为另一个类的友元。示例如下:
class Two;//先声明Two类,以便One类可以引用Two类
class One {
private:
int x;
public:
One(int a) {
x = a;
}
int getX() {
return x;
}
void func(Two&);
};
class Two {
private:
int y;
public:
Two(int a){
y = a;
}
int getY(){
return y;
}
friend void One::func(Two&);//声明One类的func函数为友元函数
};
//定义友元函数
void One::func(Two &t){
t.y = x;
}
void example6();
int main(){
example6();
return 0;
}
void example6(){
One o(10);
Two t(20);
cout << "o.x=" << o.getX() << ",t.y=" << t.getY() << endl;//o.x=10,t.y=20
o.func(t);
cout << "o.x=" << o.getX() << ",t.y=" << t.getY() << endl;//o.x=10,t.y=10
}
上例将类One的成员函数说明为类Two的友元函数,因为函数func()是属于One的,所以要使用限定符说明它的出处,One的对象就可以通过友元函数func()来访问类Two的所有成员,因为是访问类Two,所以应该使用Two对象的引用作为传递参数。
3、将一个类说明为另一个类的友元
可以将一个类说明为另一个类的友元,这时,整个类的成员函数均具有友元函数的性能。声明友元关系可以简化为“friend class 类名”。示例如下:
#include<iostream>
using std::cout;
class Four{
private:
int y;
public:
friend class Three;
};
class Three{
private:
int x;
public:
Three(int a,Four &f,int b){
x = a;
f.y = b;
}
void display(Four &f){
cout << "x=" << x << ",y=" << f.y << "\n";
}
};
void example7();
int main(){
example7();
return 0;
}
void example7(){
Four f;
Three t(10,f,20);//x=10,y=20
t.display(f);
}
友元声明与访问控制无关,友元声明在私有区域或公有区域是没有太大区别的。
四、const对象
可以在类中使用const关键字定义数据成员和成员函数或修饰一个对象,一个const对象只能访问const成员函数,否则产生编译错误。
1、常量成员
常量成员包括常量数据成员、静态常量数据成员和常量引用。静态常量数据成员仍保留静态成员特征,需要在类外进行初始化,而常量数据成员和常量引用只能通过初始化列表来获得初始值。示例如下:
#include<iostream>
using std::cout;
class Demo3 {
private:
int x;
const int y; //常量数据成员需要通过参数列表来进行初始化
static const int a; //静态常量数据成员保留静态特征,需要在类外进行初始化
const int &b;//常量引用成员需要通过参数列表进行初始化
public:
Demo3(int, int);
void display() {
cout << "x=" << x << ",y=" << y << ",a=" << a << ",b=" << b << "\n";
}
};
const int Demo3::a = 125;
Demo3::Demo3(int a,int b):x(a),y(b),b(x){//通过参数列表来定义常量数据成员和常量引用成员
}
2、常量引用作为函数参数
使用引用作为函数参数,传送的是地址,但有时仅希望将参数的值提供给函数使用,并不允许函数改变对象的值,这时可以使用常引用作为参数。例如:
void display(const int&);
3、常量对象与常量成员函数
在对象名前使用const声明常量对象,但声明时必须同时进行初始化,而且不能被更新。一个const对象只能调用const函数,不能调用非const成员函数。例如:
//下面两个常量对象的定义是等价的
Point const p1(10);
const Point p2(20);
可以声明一个成员函数为const函数,const放在函数声明之前意味着返回值是常量,这不符合语法,必须将关键字const放在函数参数列表之后,才能说明该函数是一个const成员函数。为了保证不仅声明为const成员函数,而且确实也定义为const函数,在定义时也必须重申const声明。示例如下:
#include<iostream>
using std::cout;
class Demo4 {
private:
int x, y;
const int c;
public:
Demo4(int a, int b, int c) : c(c) { //const变量只能通过列表进行初始化
x = a;
y = b;
}
void display();
void display() const; //声明const函数
};
void Demo4::display() {
cout << "x=" << x << ",y=" << y << ",c=" << c << "\n";
}
//定义const成员函数,必须再次声明
void Demo4::display() const {
cout << "x=" << x << ",y=" << y << ",c=" << c << "\n";
}
void example9();
int main(){
example9();
return 0;
}
void example9(){
Demo4 d1(10,20,30);
const Demo4 d2(10,20,30);
d1.display();//x=10,y=20,c=30
d2.display();//调用const函数,x=10,y=20,c=30
}
五、数组和类
使用类对象数组示例如下:
class Demo5{
private:
int x,y;
public:
Demo5(int a){
x = a;
}
Demo5(int a,int b){
x = a;
y = b;
}
int getX(){
return x;
}
int getY(){
return y;
}
};
void example10();
int main() {
example10();
return 0;
}
void example10() {
Demo5 d1[2] = { 10, 20 }, *p;
Demo5 d2[2] = { Demo5(1, 2), Demo5(3, 5) };
for (int i = 0; i < 2; i++) {
cout << "d1[" << i << "]=" << d1[i].getX() << endl;
}
p = d2;
for (int i = 0; i < 2; i++) {
cout << "d2[" << i << "]=" << p->getX() << "," << p->getY() << endl;
p++;
}
// d1[0]=10
// d1[1]=20
// d2[0]=1,2
// d2[1]=3,5
}
使用对象指针数组示例如下:
void example11();
int main() {
example11();
return 0;
}
void example11(){
Demo5 *d1[2] = {new Demo5(10),new Demo5(20)};
Demo5 *d2[2] = {new Demo5(1,3),new Demo5(2,4)};
for(int i=0;i<2;i++){
cout << "d1[" << i << "]=" << d1[i]->getX() << endl;
}
for(int i=0;i<2;i++){
cout << "d2[" << i << "]=" << d2[i]->getX() << "," << d2[i]->getY() << endl;
}
// d1[0]=10
// d1[1]=20
// d2[0]=1,3
// d2[1]=2,4
}
六、指向类成员函数的指针
对象是一个完整的实体,为了支持这一封装,C++包含了指向类成员的指针,可以用普通指针访问内存中给定类型的对象,指向类成员的指针则用来访问某个特定类的对象中给定类型的任何成员。示例如下:
#include<iostream>
using std::cout;
class Demo6{
private:
int x;
public:
Demo6(int a){
x = a;
}
int getResult(int a){
return x + a;
}
};
void example12();
int main() {
example12();
return 0;
}
void example12() {
int (Demo6::*p)(int); //声明指向类Demo6的成员函数的指针
p = Demo6::getResult; //指针指向具体的成员函数getResult
Demo6 obj(10); //创建对象obj
cout << (obj.*p)(15) << endl; //对象使用类的函数指针,输出结果:25
Demo6 *p2 = &obj; //声明对象obj的指针
cout << (p2->*p)(15); //对象指针使用类的函数指针,输出结果:25
}
在使用类的成员函数的指针访问对象的某个成员函数时,必须指定一个对象。使用对象名或引用调用指针所指向的函数时,使用运算符".*",使用指向对象的指针调用指针所指向的成员函数时,使用运算符"->*"。