C++ Primer 学习笔记_20_类与数据抽象(2)_内联成员函数、成员函数的重载及其缺省参数、类与结构体、隐含的this指针
一、内联成员函数
1、概念
内联函数作用:提高效率
编译的时候将代码直接嵌入到调用的地方,从而减少了函数调用的开销。但是程序的体积增大,以空间换时间。
一般内联函数是比较短小的。
内联函数仅仅只是给编译器一个提示,不一定会内联展开。比如如果函数有switch或for时,编译器可能就不会以内联方式展开。
2、inline的示例
在类内部定义的成员函数,默认就是inline函数。但是也可以显式的将成员函数指定为inline:
class Screen { public: typedef std::string::size_type index; //方式一:类内的函数默认就是inline函数 char get() const { return contents[cursor]; } //方式二: inline char get(index ht,index wd) const; //方式三: index get_cursor() const; private: std::string contents; index cursor; index height,width; }; //已经在类体中声明为inline了,就没必要再次声明 char Screen::get(index r,index c)const { index row = r * width; return contents[row + c]; } //即使没有在类体中声明,也可以在外面补上 inline Screen::indexScreen::get_cursor() const { return cursor; }
在声明和定义处指定inline都是合法的。在类的外部定义 inline 的一个好处是可以使得类比较容易阅读。
【注解】
像其他inline一样,inline成员函数的定义必须在调用该函数的每个源文件中是可见的。不在类定义体内定义的inline成员函数,其定义通常应放在有类定义的同一头文件中。
二、成员函数的重载及其缺省参数
1、成员函数可以被重载
成员函数只能重载本类的其他成员函数。
重载的成员函数和普通函数应用相同的规则:两个重载成员的形参数量和类型不能完全相同。调用非成员重载函数所用到的函数匹配过程也应用于重载成员函数的调用。
2、示例
//Test.h #ifndef _TEST_H_ #define _TEST_H_ class Test { public: void Init(); void Init(int x); void Init(int x, int y); void Init(int x, int y, int z); void Display(); private: int x_; int y_; int z_; }; #endif // _TEST_H_
//Test.cpp #include "Test.h" #include <iostream> using namespace std; void Test::Init() { x_ = 0; y_ = 0; z_ = 0; } void Test::Init(int x) { x_ = x; y_ = 0; z_ = 0; } void Test::Init(int x, int y) { x_ = x; y_ = y; z_ = 0; } void Test::Init(int x, int y, int z) { x_ = x; y_ = y; z_ = z; } void Test::Display() { cout<<"x="<<x_<<" y="<<y_<<" z="<<z_<<endl; }
//main.cpp #include "Test.h" int main(void) { Test t; t.Init(); t.Display(); t.Init(10); t.Display(); return 0; }
3、缺省参数(修改上述代码)
可以观察到在Init函数部分有细微的差别,但效果是一样的。
//Test.h #ifndef _TEST_H_ #define _TEST_H_ class Test { public: void Init(int x=0, int y=0, int z=0); void Display(); private: int x_; int y_; int z_; }; #endif // _TEST_H_
//Test.cpp #include "Test.h" #include <iostream> using namespace std; void Test::Init(int x/* =0 */, int y/* =0 */, int z/* =0 */) { x_ = x; y_ = y; z_ = z; } void Test::Display() { cout<<"x="<<x_<<" y="<<y_<<" z="<<z_<<endl; }
//main.cpp #include "Test.h" int main(void) { Test t; t.Init(); t.Display(); t.Init(10); t.Display(); return 0; }
4、二义性:在参数有缺省值时应注意
比如在上面的程序中, 除了定义了void Init(int x=0, int y=0, int z=0)函数,还定义了void Init()函数,因此在调用 t.Init(); 时会有二义性的错误产生,原因是存在两个函数匹配。
三、类与结构体
struct S { int X;//公有的 ... }
class C { int X;//私有的 ... }
四、隐含的this指针
1、引言
在前面提到过,成员函数具有一个附加的隐含形参,即指向该类对象的一个指针。这个隐含形参命名为this。
2、返回*this
(1)成员函数有一个隐含的附加形参,即指向该对象的指针,这个隐含的形参叫做this指针(编译器自动传递)
(2)使用this指针保证了每个对象可以拥有不同数值的数据成员,但处理这些成员的代码可以被所有对象共享。
(3)成员函数是只读的代码,由所有对象共享,并不占对象的存储空间(每个对象都有自己的内存),因为this指针指向当前对象,所以成员函数可以区分它所作用的对象是哪一个。
(哪个对象调用了this所在的函数,this就代表哪个对象)从上述可以看到,t1.Init(10, 20, 30);实际上是传递了一个隐含的指针,保证了每个对象可以拥有不同数值的数据成员,但处理这些成员的代码可以被所有对象共享。我们可以将this指针显式地表达出来。
void Test::Init(int x/* =0 */, int y/* =0 */, int z/* =0 */) { this->x_ = x; this->y_ = y; this->z_ = z; }
3、从const成员返回*this
在普通的非const成员函数中,this的类型是一个指向类类型的const指针。可以改变this所指向的值,但不能改变this所保存的地址。在const成员函数中,this的类型是一个指向const类类型对象的const指针。既不能改变this所指向的对象,也不能改变this所保存的地址。
不能从const成员函数返回指向类对象的普通引用。如果将display作为 Screen的 const成员,则 display内部的 this指针将是一个constScreen* 型的const。然而:
myScreen.move(4,0).set('#').display(cout); //OK myScreen.display().set('*'); //Error
4、可变数据成员
有时,我们希望类的数据成员(甚至是在const成员函数中)可以修改。这可以通过将它们声明为mutable来实现。
可变数据成员永远都不能为const,甚至当它们是const对象的成员时也如此。因此,const成员函数可以改变mutable成员。
class Screen { public: //... private: mutable size_t access_ctr; //使用access_ctr来跟踪Screen成员函数的调用频度 void do_display(std::ostream &os) const { ++ access_ctr; //OK os << contents; } };
【实践:看一道经典的题目】
class A { public: int m; voidprint() { cout << "A" << endl; } }; A *pa = 0; pa->print();
相当于成员函数传递的this指针为0,那调用会出错吗? 肯定是正确输出"A"的,因为this为0 表示没有对某个对象进行操作,而print里面确实没有对某个对象成员进行操作,所以是可以运行的。