目录
内联函数
● 任何在类中定义的函数自动地成为内联函数,也可以使用inline 关键字放在类外定义函数前面使之成为内联函数,但是必须使函数体和声明结合在一起, 否则, 编译器只将这个函数视为普通成员函数。 例如:
class Rect
{
int width();
};
inline int Rect::width()
{
}
还可以这样定义内联函数:
class Rect
{
inline int width();
};
int Rect::width()
{
}
● 实现内联成员函数的另一种办法是在类声明的头文件中加入函数的定义。
● 注意: 使用内联函数可以使函数的执行效率提高,但是使用过多的内联函数可能会使代码膨胀,如果将类的析构函数定义为内联函数,可能会导致潜在的代码膨胀。
另外,使用过多的内联函数会占用大量内存空间,使效率降低, 因为对于内联函数来说, 程序会在函数调用的地方直接插入函数代码,所以一般将少数调用频繁的成员函数设置为内联函数,或者说某个成员函数的代码只有一两句 使之成员内联函数, 这样提高程序的执行效率。
友元
● 在C++中, 为了使类的私有成员和保护成员能够被其他类(其中的成员函数)或者其他非成员函数 访问, 引入了友元的概念。
友元提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制
● 如果友元是非成员函数 或者类的成员函数,则成为友元函数; 如果友元是一个类,则成为友元类, 友元类中的所有成员函数都称为另一个类的友元函数。
● 有时需要别的类的成员函数 或者 普通函数 访问某一个类的数据成员, 可以把这些函数定义为该类的友元函数。 友元的作用是提高程序的效率, 但是破坏了类的封装性 和 隐藏性,使得外部函数可以访问某类的私有和保护成员。
● 我们通常将把友元的声明与类本身放置同一个头文件中。
友元函数
● 友元函数它不是当前类的成员函数,它不属于任何类,而是定义在类外的普通函数。 友元函数可以是普通函数 或者其他类的成员函数, 是可以直接访问某个类的私有成员的非成员函数。
● 友元函数使用前必须要在类的定义中加以声明,表示该函数可以是友元函数,可以访问该类中的所有数据成员。
声明语法为:
friend 数据类型 标识符();
● 友元函数的声明可以在类定义中的任何位置,没有任何区别。 一个函数可以是多个类的友元函数,只需要在各个类中分别声明。
● 友元函数的调用与一般函数的调用方式和原理一致。
● 我们首先看下普通函数是如何访问类的公有成员数据(不能访问私有和保护成员):
class Rect
{
public:
Rect(int i, int j)
{
width = i;
height = j;
}
int getHeight()
{
return height;
}
int getWidth()
{
return width;
}
private:
int width, height;
};
int rectArea(Rect &myRect)普通函数访问类中的私有数据,
{ 在入口参数传递对象名 或者 对象指针来引用该对象的成员
return myRect.getHeight() * myRect.getWidth();
}
int main()
{
Rect rg(100, 500);
cout << "输出结果为:" << rectArea(rg) << endl;
system("pause");
return 0;
}
● 此时我们只能通过该类的公有属性的成员函数来获得该类的 私有成员数据
● 下面看把普通函数定义友元函数的例子:
class Rect
{
public:
Rect(int i, int j)
{
width = i;
height = j;
}
friend int rectArea(Rect &myRect); //该函数如果定义在内部,就是隐式内联的
private:
int width, height;
};
int rectArea(Rect &myRect)友元函数访问类中的私有数据
{ 在入口参数传递对象名 或者 对象指针来引用该对象的成员
return myRect.height * myRect.width;
}
int main()
{
Rect rg(100, 500);
cout << "输出结果为:" << rectArea(rg) << endl;
system("pause");
return 0;
}
现在看到我们在 rectArea () 函数的定义中 Rect 的对象可以直接引用其中的数据成员, 这是因为在该类中将此函数声明为友元了。
再看例子:
class Rect
{
public:
Rect(int i, int j)
{
width = i;
height = j;
}
friend void rectArea(void *p); 设置友元函数
private:
int width, height;
};
void rectArea(void *p)友元函数访问类中的私有数据
{ 在入口参数传递对象名 或者 对象指针来引用该对象的成员
Rect *temp = (Rect*)p; 得到对象指针
cout << "输出结果为" << (temp->height*temp->width) << endl;
}
int main()
{
Rect rg(100, 500);
rectArea(&rg);
system("pause");
return 0;
}
在此可以看到使用友元保持了Rect 该类中的数据的私有性, 起到了隐藏数据成员的好处, 又使得特定的类或函数可以直接访问这些隐藏的数据成员。
● 一般来说使用友元函数应该注意的问题:
● 友元函数不是成员函数,类外定义不必加 “ 类名 ::”
● 友元函数不是类的成员,不能直接引用对象成员的名字, 也不能通过this指针引用对象的名字, 必须通过作为入口参数传递进来的对象名 或者 对象指针 来引用该对象的成员。 因此,友元函数一般都带有一个该类的入口参数, 如上列的rectArea(Rect &myRect)
● 当一个非成员函数 或者其他类的成员函数需要访问多个类时, 把这个函数同时定义微这些类的友元函数,这样才能访问这些类的数据。
友元类
● 在开发程序时,如果两个类的耦合度比较紧密, 能够在一个类中访问另一个的私有成员会带来很大的方便。 C++ 语言提供了 友元和友元成员函数 来实现访问其他类的私有成员。
当开发者希望另一个类能够访问当前类的私有成员时, 可以在当前类的定义中将另一个类声明为自己的友元类, 这样在另一个类中就可以访问当前类的私有成员了。
● 友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的所有成员。
下面看一个另一个类作为当前类的友元的例子:
class Cltem
{
private:
char name[128];
void outputName()
{
cout << name << endl;
}
public:
friend class CList; 将CList类作为自己的友元类
void setName(const char *datas)
{
if (datas != nullptr)
{
strcpy_s(name, datas);
}
}
};
class CList
{
private:
Cltem myCltem;
public:
void outputltem()
{
myCltem.setName("huang"); 调用Cltem 类的公有函数
myCltem.outputName(); 调用Cltem 类的私有函数
}
};
int main()
{
CList myCList;
myCList.outputltem();
system("pause");
return 0;
}
使用友元类应该注意的问题:
● 友元类不能被继承, 也就是说,CList 的子类 不是 Cltem 的友元类
● 友元关系是单向的, 不具有交换性; 若类 CList 是Cltem 的友元类, Cltem 不一定是 CList 的友元类,这个是由类是否有相应的声明决定。
● 友元关系不具有传递性,若类 CList 是Cltem 的友元类,类Temp 是CList 类的友元类,类Temp 不一定是Cltem 的友元类,这个是由类是否有相应的声明决定。
友元成员函数
● 在开发程序时, 有时需要另一个类对当前类的私有成员的方法。 例如, 假设需要实现只允许 CList 类的某个成员函数 访问 Cltem 类的私有成员, 而不允许其他CList类中的成员函数访问 Cltem 类的私有数据, 便可以通过定义友元函数来实现。
在定义 Cltem 类时, 可以将CList 类的某个方法定义为友元方法, 这样就限制了只有该方法允许访问 CItem 类的私有成员。
● 如果一个类的成员函数是另一个类的友元函数, 通过友元成员函数不仅可以访问自己所在类对象中的所有其他成员, 还可以由关键字friend 声明语句所在的类对象中的所有成员。
下面看一个代码示例:
class Cltem; 前导声明 Cltem 类
class CList
{
private:
Cltem *myCltem;
public:
CList();
~CList();
void outputltem();
};
class Cltem
{
private:
friend void CList::outputltem(); 定义outputltem 为 Cltem 类的友元成员函数
char name[128];
void outputName()
{
cout << name << endl;
}
public:
void setName(const char *datas)
{
if (datas != nullptr)
{
strcpy_s(name, datas);
}
}
};
void CList::outputltem()
{
myCltem->setName("huang");
myCltem->outputName(); 访问了 CItem 类的私有方法
}
CList::CList()
{
myCltem = new Cltem;
}
CList::~CList()
{
delete myCltem;
myCltem = nullptr;
}
int main()
{
CList myCList;
myCList.outputltem();
system("pause");
return 0;
}
注意: 当一个类的成员函数作为另一个类的友元函数时, 必须先定义成员函数所在的类, 如上述代码类CList 的成员函数 outputltem() 为类 Cltem 的友元函数, 就必须先定义类CList; 并且在声明友元函数时, 要加上这个成员函数所在类的类名和作用域运算符 “ :: " , 如上例语句:
friend void CList::outputltem(); 定义友元成员函数
另外,在主函数中一定要创建类 CList 的一个实例对象, 只有这样才能通过对象名调用友元函数。
最后,如果在类定义前要使用该类的成员, 需要在使用前对该类进行声明, 如上述声明语句
class Cltem; 前导声明 Cltem 类
否则系统会报错。
注意: 如果一个类想把一组重载函数声明某一个类的友元成员函数,那么需要对这组重载函数分别声明friend。
下面再看一个例子:
class Cltem
{
friend void outPut(Cltem *myCltem); 声明outPut为友元函数
private:
char name[128];
void outputName()
{
cout << name << endl;
}
public:
void setName(const char *datas)
{
if (datas != nullptr)
{
strcpy_s(name, datas);
}
}
};
void outPut(Cltem *myCltem)
{
if (myCltem != nullptr)
{
myCltem->setName("huang"); 调用Cltem 的公有方法
myCltem->outputName(); 调用Cltem 的私有方法
}
}
int main()
{
Cltem myCltem;
outPut(&myCltem); 通过友元函数访问Cltem 类的私有方法
system("pause");
return 0;
}
友元声明和作用域
class x
{
public:
friend void f() {/*友元函数可以定义在类的内部*/} // 友元本身不一定真的声明在当前作用域中
x() { f(); } // 错误: f还没有被声明
void g();
void h();
};
void x::g() // 错误: f还没有被声明
{
return f();
}
void f(); //声明那个定义在x中的函数
void x::h() // 正确: 现在 f的声明在作用域中了
{
return f();
}
● 类和非成员函数的声明不是必须在它们的友元声明之前。
甚至就算在类的内部定义了该函数,我们也必须在类的外部提供相应的声明从而使得函数可见。 换句话说,即使我们仅仅是用声明友元的类的成员调用该友元函数,它也必须是被声明过的
const 成员函数
● 使用 const 成员函数 要注意的问题有:
● 常成员函数不能更改对象中数据成员的值, 也不能调用非类常成员函数。
● 如果将该类实例化的对象声明为 常对象, 那么这个对象只能调用常成员函数。
● 对于常成员函数,后面的const 关键字 可以作为重载函数的区分。
● 要同时在成员函数的声明和定义中指明const 限定符
● 如果必须在const 成员函数中修改某些数据的值,正确的方法是:声明这些数据成员时添加前辍 mutable 限定符,这样即使在const 成员函数中也能修改成员数据的值。
一个 mutable 成员数据永远不会是const,即使该成员数据是 const对象的成员。
● const 成员函数 不能在它的实现中调用另一个 非const 成员函数。
● 一个非常对象可以调用const成员函数。
string Sales_data::isbn()const
{
return bookNo;
}
● isbn() 函数的参数列表之后的
const
关键字。const
的作用是修改隐式this
指针的类型(const Sales_data *const),如果说是非常成员函数,那么该函数的this指针的类型是(Sales_data *const)。● 在默认情况下,
this
的类型是指向类类型非常量版本的常量指针 (意识就是说, 该指针是个常量,即该指针不能修改,但是该指针指向的对象成员的值可以修改),尽管this
是隐式的,但它仍然需要遵循初始化规则,意味着(在默认情况下) 我们不能把this
绑定到一个常量对象上。
● 这一情况也就使的我们不能在一个常量对象上调用普通的成员函数。 在C++语言中允许把const关键字放在成员函数列表之后,此时,紧跟在参数列表后面的const 表示this 是一个指向常量的指针(const Sales_data *const)。像这样使用const的成员函数被称作常量成员函数。
string isbn(const Sales_data *const this)
{
return this->bookNo;
}
注意: 上面的代码是非法的,因为我们不能显式定义自己的this指针,此处的this是一个指向常量的指针,因为isbn 是一个常量成员
因为此处的this 指针是指向常量的指针, 所以常量成员函数不能改变调用它的对象的内容。
注意 : 常量对象,以及常量对象的引用或指针都只能调用常量成员函数。
● 注意: 不同于其他成员函数, 构造函数不能被声明成const
的和 static , 当我们创建类的一个const
对象时,直到构造函数完成初始化过程,对象才能被真正取得其“常量”属性。因此,构造函数在const
对象的构造过程中可以向其写值。
const Screen &display(ostream &os)const
{
do_display(os);
return *this;
}
上述代码,如果一个const成员函数 以引用的形式返回 *this,那么该函数的返回类型是 常量引用。
下面看一个简单的代码示例:
class Line
{
public:
Line(int x1, int x2, int y1, int y2);
void setpoint1(int x, int y);
void setpoint2(int x, int y);
void Draw()const;
void Draw();
private:
int m_x1, m_x2, m_y1, m_y2;
};
Line::Line(int x1, int x2, int y1, int y2)
{
m_x1 = x1;
m_x2 = x2;
m_y1 = y1;
m_y2 = y2;
}
void Line::setpoint1(int x, int y)
{
m_x1 = x;
m_y1 = y;
}
void Line::setpoint2(int x, int y)
{
m_x2 = x;
m_y2 = y;
}
void Line::Draw()const
{
cout << "point1(" << m_x1 << "," << m_y1 << ")" << \
" " << "point2(" << m_x2 << "," << m_y2 << ")" << endl;
}
void Line::Draw()
{
cout << "point1(" << m_x1 << "," << m_y1 << ")" << \
" " << "point2(" << m_x2 << "," << m_y2 << ")" << endl;
}
int main()
{
const Line In(0, 20, 200, 100); 创建常对象
In.setpoint1(0, 0); 错误, 因为企图更改对象中成员变量的值
In.Draw(); 正确,调用const 成员函数
Line In2(0, 20, 200, 100);
In2.Draw(); 调用非常成员函数 Draw()
system("pause");
return 0;
}
基于const的重载 (C++ primer 248页)
● 通过区分成员函数是否是const的,我们可以对其进行重载。
其原因为: 因为非常量版本的函数对于常量对象是不可用的,所以我们只能在一个常量对象上调用const
成员函数。
另一方面,虽然可以在非常量对象上调用常量版本或非常量版本, 但显然此时非常量版本是一个更好的匹配。
class Screen
{
public:
Screen &display(ostream &os)
{
do_display(os);
return *this;
}
const Screen &display(ostream &os)const
{
do_display(os);
return *this;
}
private:
void do_display(ostream &os)const
{
os << contents;
}
}
● 当display的非常量版本调用do_display时,它的this指针隐式地从指向非常量的指针转换成指向常量的指针.
● 当do_display 完成后, display 函数各自返回解引用this所得的对象。 在非常量版本中,this 指向一个非常量对象, 因此display返回一个普通的(非常量)引用; 而const成员则返回一个常量引用。
● 当我们在某个对象上调用display时,该对象是否是const决定了应该调用display的哪个版本。
Screen myScreen(5,3);
const Screen blank(5,3);
myScreen.set('#').display(count); //调用非常量版本
blank。display(cout); //调用常量版本
编译器如何实现 const 成员函数
● 编译器如何检测到成员函数为为数据成员赋值?
这非常简单, 数据成员和成员函数之间唯一的连接就是this指针。 const成员函数必须把调用它的对象当做const 对象, 这也可以通过将this 指针声明为 指向const 的指针轻松做到, 例如:
unsigned HowMany(const TIntStack * const this);
根据这个声明, 任何通过指针给对象内部的数据成员赋值都是非法的,因为该this指针是一个指向常量的指针。