一. C++对象模型和this指针
1. 成员变量和成员函数分开存储
在C++中,类内的成员变量和成员函数分开存储;
只有非静态成员变量才属于类的对象上,占对象空间;
另外,空对象占用内存空间为1
C++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置,每一个空对象也应该有一个独一无二的内存地址。
2. this指针概念
在C++中成员变量和成员函数是分开存储的,每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。那么问题是:这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的对象指针,this指针,解决上述问题。
this指针指向被调用的成员函数的所属对象,this指针是隐含在每一个非静态成员函数内的一种指针,this指针不需要定义,直接使用即可。
this指针的用途:
1)当形参和成员变量同名时,可用this指针区分;
2)在类的非静态成员函数中返回对象本身,可使用return * this 。
示例:
class Person
{
public:
Person (int age)
{
//1.当形参和成员变量同名时,可用this指针区分
this->age=age;
}
Person & PersonAddPerson(Person p) //返回本体用引用的方式做一个返回
{
this->age+=p.age;
return *this; //返回对象本身
/*若改为Person PersonAddPerson(Person p)以值的方式返回,
则会创立一个新的对象,调用了拷贝构造函数*/
}
int age;
};
void test01()
{
Person p1(10);
cout<<"p1.age="<<p1.age<<endl;
Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout<<"p2.age="<<p2.age<<endl; //输出为40
//若采用Person PersonAddPerson(Person p),此处输出则为20
}
int main()
{
... ...
}
3. 空指针访问成员函数
C++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针,如果用到this指针,需要加以判断保证代码的健壮性。
//空指针访问成员函数
class Person
{
public:
void showclassname()
{
cout<<"我是 Person类"<<endl;
}
void showperson()
{
if(this==NULL) /*判断语句作用:若this为NULL,退出;若不为NULL,打印输出;
保证代码健壮性,防止报错*/
{
return;
}
cout<<mAge<<endl; //相当于this->mAge
}
public:
int mAge;
};
void test01()
{
Person*p=NULL;
p->showclassname(); //空指针,可以调用成员函数
p->showperson(); //但是如果成员函数用到了this指针,就不可以了。此处若没有if语句,会报错
}
int main()
{
... ...
}
4. const修饰成员函数
常函数:成员函数后加const后我们称这个函数为常函数,常函数内不可以修饰成员属性,成员属性声明时加关键字mutable后,在常函数中依然可以修改。
常对象:声明对象前加const称该对象为常对象,常对象只能调用常函数
示例:
class Person
{
public:
Person()
{
m_A=0;
m_B=0;
}
/*this指针的本质是一个指针常量,指针的指向不可以修改,
如果想让指针指向的值也不可以修改,需要声明常函数。*/
void showPerson() const //加const相当于const Person*const this
{
//const Type* const pointer;
//this=NULL; 不可以修改指针的指向
//this->m_A=100;
/*常函数内不可以修改成员属性,但是this指针指向的对象的数据
是可以修改的,m_A=100等价于this->m_A=100*/
//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
this->m_B=100;
}
void MyFunc() const
{
//m_A=10000;
}
public:
int m_A;
mutable int m_B; //可修改,可变的
};
//const 修饰对象,常对象
void test01()
{
const Person person; //常对象
cout<<person.m_A<<endl;
//person.m_A=100; 常对象不能修改成员变量的值,但是可以访问
person.m_B=100; //但是常对象可以修改mutable修饰成员变量
//常对象访问成员函数
person.MyFunc(); /*常对象只能调用const修饰的函数,不能调用普通成员函数,
因为普通成员函数可以修改属性*/
}
int main()
{
... ...
}
二. 友元
在程序中,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。
友元机制允许一个类将对其非公有成员的访问权授予指定的函数或者类,友元的声明以friend开始,它只能出现在类定义的内部,友元声明可以出现在类中的任何地方。
友元的三种实现:1)全局函数做友元;2)类做友元;3)成员函数做友元
1. 友元函数(全局函数做友元)
友元函数是指某些虽然不是类成员函数却能够访问类的所有成员的函数。类授予它的友元特别的访问权,这样该友元函数就能访问到类中的所有成员。
#include <iostream>
using namespace std;
class A
{
public:
friend void set_show(int x, A &a); //该函数是友元函数的声明
private:
int data;
};
void set_show(int x, A &a) //友元函数定义,为了访问类A中的成员
{
a.data = x;
cout << a.data << endl;
}
int main(void)
{
class A a;
set_show(1, a);
return 0;
}
2. 友元类 (类做友元)
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。
关于友元类的注意事项:
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的声明。
#include <iostream>
using namespace std;
class A
{
public:
friend class C; //这是友元类的声明
private:
int data;
};
class C //友元类定义,为了访问类A中的成员
{
public:
void set_show(int x, A &a) { a.data = x; cout<<a.data<<endl;}
};
int main(void)
{
class A a;
class C c;
c.set_show(1, a);
return 0;
}
3. 友元成员函数(成员函数做友元)
使类B中的成员函数成为类A的友元函数,这样类B的该成员函数就可以访问类A的所有成员了。
当用到友元成员函数时,需注意友元声明和友元定义之间的相互依赖,在该例子中,类B必须先定义,否则类A就不能将一个B的函数指定为友元。然而,只有在定义了类A之后,才能定义类B的该成员函数。更一般的讲,必须先定义包含成员函数的类,才能将成员函数设为友元。另一方面,不必预先声明类和非成员函数来将它们设为友元。
#include <iostream>
using namespace std;
class A; //当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。这是类A的声明
class B
{
public:
void set_show(int x, A &a); //该函数是类A的友元函数
};
class A
{
public:
friend void B::set_show(int x, A &a); //该函数是友元成员函数的声明
private:
int data;
void show() { cout << data << endl; }
};
void B::set_show(int x, A &a) /*只有在定义类A后才能定义该函数,毕竟,
它被设为友元是为了访问类A的成员*/
{
a.data = x;
cout << a.data << endl;
}
int main(void)
{
class A a;
class B b;
b.set_show(1, a);
return 0;
}
4. 小结
在需要允许某些特定的非成员函数访问一个类的私有成员(及受保护成员),而同时仍阻止一般的访问的情况下,友元是可用的。
优点:
- 可以灵活地实现需要访问若干类的私有或受保护的成员才能完成的任务;
- 便于与其他不支持类概念的语言(如C语言、汇编等)进行混合编程;
- 通过使用友元函数重载可以更自然地使用C++语言的IO流库。
缺点:
一个类将对其非公有成员的访问权限授予其他函数或者类,会破坏该类的封装性,降低该类的可靠性和可维护性。