本文是我在学习c++过程当中的一些思考和总结,主要是c++中关于函数的林林总总。欢迎你们批评和指正,共同窗习。html
os version: ubuntu 12.04 LTS gcc version: gcc 4.6.3 文中以 $ 开头语句表示 shell command
0.this 指针c++
我以为首先得讲明白这个东东,让你们明白c++中函数与c语言中函数的区别
什么是 this 指针? 这里我直接选自 ISO c++ 中关于 this 定义(注:我会大量援引ISO c++,相信你们应该都看得懂,哈哈)shell
In the body of a non-static member function, the this is a prvalue expression whose value is a address of the object for which the function is called.
这个定义里面,咱们须要注意两点:
(1) 只有类中的 non-static member function 才有隐含的 this 指针,这样 类中的 static 函数、不属于类的全局函数 都没有 this 指针
数据库
(2) this 是常指针,一直指向调用该函数的类对象,其指向(地址值)不可更改,即 Widget* const thisexpress
class Widget { public: int fun() { return a + b; } private: int a; int b; };
c++编译器会将 member function 转化为对等 non-member function
a.改写函数原型,安插额外的隐含参数到 member function,用以提供一个存储通道,使得该类的全部对象均可以调用该函数编程
int Widget::fun(Widget* const this)
b.将每个 non-static data member 的存取操做改写成 经由 this 指针来存取ubuntu
int Widget::fun(Widget* const this) { return this->a + this->b; }
注:只有属于类而且 non-static 的 data member 才由 this 指针来存取,若是这样:int fun(int x, int y) { return x + y; }, 则会以下改写:socket
int Widget::fun(Widget* const this, int x, int y) { return x + y; }
c.将 member function 从新写成一个外部函数,对函数进行 "name mangling"处理,使它在程序中成为独一无二的词汇
注: name mangling 确保每个符号都有惟一的名字,函数重载部分我会详细讲讲这个
咱们对示例代码 cpp_function.cc :函数
$ g++ cpp_function.cc -o cpp_function.o -W -g -c -std=c++0x
咱们对生成的 cpp_function.o: 学习
$ nm cpp_function.o | grep -E fun
能够将 "name mangling"视为编码过程,固然有对应的解码过程"demangling"
$ c++filt _ZN6Widget3funEv
d.如今假如咱们调用 fun 函数:
成员调用符:widget.fun(); 其实是: fun(&widget);
指针调用: pwidget->fun(); 其实是: fun(pwidget);
知识小结:
0.1 任何一个non-static data member 都是且只能经过this指针存取的
0.2 编译器在每一个 non-static 的 member function的第一个参数都安插一个隐藏this指针参数
0.3 调用 non-static non-virtual 的 member function:
// non-virtual function 调用不须要经过this寻址,而是经过编译器的符号表 widget.fun() ==> Widget_3fun(&widget); //编译器安插一个this指针 pwidget->fun() ==> Widget_3fun(pwidget); //编译器安插一个this指针
0.4 调用 virtual member function:
// virtual function 调用首先须要经过this指针、vptr虚表指针寻址 widget.vfun() ==> (*(widget.vptr[1])) (&widget) //&widget为this指针,1为vfun虚表下标 pwidget->vfun() ==> (*(pwidget->vptr[1])) (pwidget)
0.5 调用 static member function:
static member function 无 额外安插的this指针,所以: a.不能存取 non-static data member (这些data必须经过this指针存取) b.不能为 virtual function (virtual function都是有 this 有vptr的) c.不能为 const member function(const member function 都是 const Widget* const this) widget.sfun() ==> Widget_4fun(); // 无额外安插的this指针 pwidget.sfun() ==> Widget_4fun(); // 无额外安插的this指针
好吧,this 指针这部分暂时告一段落吧
1. inline 函数
可能有的同窗以为 inline 函数很简单,其实这里面仍是有点门道的
当函数被声明 inline 函数以后,编译器可能会将其内联展开,无需按照一般的函数调用机制调用 inline 函数
这里我重点突出了两个字"可能",难道我将一个函数声明为 inline,编译器敢违背我命令不进行内联展开吗? 是的,彻底可能
inline 声明对编译器来讲只是一个建议,编译器能够选择忽略这个建议
优势: inline 函数能够避免函数调用的开销,令目标码更加高效
缺点:inline 一个很大的函数将增长目标码大小
究竟哪些函数应该声明 inline?
Google c++ 编程规范里面: 只将小于10行的函数进行 inline
(1)编译器隐式地将在类内定义的成员函数看成 inline 函数(注:建议在类定义函数时 显式声明为 inline,喜欢她就要最大声最直白的表达出来,哈哈)
(2)inline 函数应该在头文件中定义,确保调用函数时所使用的定义是相同的,而且编译器对 inline 函数的定义保持可见
(3)不要将内含循环语句的函数 inline
(4)大多数编译器不支持递归函数 inline
(5)大多数编译器不支持 virtual 函数 inline (由于 virtual 函数意味着 运行时肯定调用,而 inline 函数须要编译时内联展开)
(6)编译器通常不支持 构造函数 和 析构函数 inline (由于这两个函数实际上作的工做比咱们想象的多,尤为是涉及到继承时)
(7)编译器通常不支持 "经过函数指针而进行的调用" 实施 inline
c++ sort 经过函数对象(将 operator()函数 inline)进行比较 快于 c语言中qsort经过自定义comp函数指针进行比较 (详见Effective STL 第46条)
2. 函数重载
ISO c++: Two delarations in the same scope that declare the same name but with different types are called overloaded
a. 必定要出如今相同做用域
b. 函数具备相同的名字可是函数原型必定不相同
什么是函数原型?
函数原型 = 函数名 + 函数参数个数 + 函数参数类型
这里咱们能够知道 仅仅靠 函数返回值类型不一样 或者 函数参数名称不一样 都不能 构成 重载
咱们在第0部分提到过: 编译器经过 "name mangling" 确保每个符号都有惟一的独一无二的名字
class Widget { public: int fun(int a, int b); int fun(double a, double b); int fun(int a, int b, int c); };
咱们由图中能够看到 通过 name mangling 事后,每一个符号名字带有:
类名信息、函数名长度、函数名、全部参数的第一个字母缩写
若是咱们 添加诸如: int fun(int c, int d); double fun(int a, int b) 都是编译错误
咱们再来看看这3组:
//第一组
int fun(int a); int fun(const int a); //第二组
int fun(int* a); int fun(const int* a); //第3组
int fun(int& a); int fun(const int& a);
上图中三次编译分别依次对应于上述三组状况
咱们能够看到:
第1组编译错误,不能重载
第2组构成重载,经过符号名字能够大概猜想到: P表明 pointer,K表明 const
第3组构成重载,经过符号名字能够大概猜想到:R表明 reference, K表明 const
首先我说说 c++中的值语义和对象语义,这部分具体能够详见 http://www.cnblogs.com/solstice/archive/2011/08/16/2141515.html
值语义: 对象的拷贝与原对象无关,两个对象拷贝以后互相分离,彼此无关。c++的内置类型(bool/int/double/char)都是值语义 eg:int a; int b; a = b; //b值赋值给a后,a与b彼此分离
对象语义:对象拷贝以后与原对象并不分离,而是共享同一资源。c++指针、引用、含有各类资源(内存、文件描述符、socket、TCP链接、数据库链接等)的对象 eg:int* a; int* b; b = a; //b值(地址)赋值给a后,a与b并未分离,而是共同指向同一地址
引用也是如此,做用在引用上的全部操做事实上都是做用在该引用绑定的对象上。其实引用最终是靠指针实现的.
好了,咱们再来讲这3组
(1)第一组,根据编译器给出的错误提示,感受编译器直接把 const 吞了,有没有 const 都同样,好吧,const 在这里好没存在感,哈哈
第一组参数既不是指针,也不是引用,绝对的值语义对象。现假设传入实参 int b = 1;
两个函数都发生对实参对象的拷贝,拷贝以后,实参与形参分离,两个函数都是在操做实参的拷贝,并且这个拷贝已经跟实参没有任何联系了
这样一来,这两个函数对于 实参 来讲,具备彻底相同的语义,并没有本质区别,编译器固然得制止这种行为,否则显得太弱智了...
(2)第二组和第三组的缘由其实相同,这里我只解释第2组
经过上面介绍,咱们知道第2组传递的参数具备对象语义. 现假设传入实参 &b
第一个函数发生以下的实参拷贝: int* a = &b;
第二个函数发生以下的实参拷贝: const int* a = &b;(注:non-const 对象地址能够赋值给 const 指针,隐式转换. 只能 non-const ==> const 反之错误)
拷贝以后,实参与形参没有分离,而是共同指向同一地址
可是,这两个函数有不一样的语义:
第一个函数能够经过形参a 更改 实参b的值,好比:*a = 2; //这时 实参b所指元素变成了2
第二个函数不能经过形参a 更改 实参b的值,由于 const int* a(a is a pointer to const int),a所指元素为 const,不可修改
这样一来,这两个函数对于 实参 来讲,具备不一样的语义(一个能够改变实参,另外一个不能够),有本质区别,编译器得容许这种行为.
3. const member function
为何我会在将函数重载以后将这个呢? 哈哈,固然是有因果关系!
class Widget { public: int fun(int a) {...} int fun(int b) const {...} };
当咱们写出这样的代码,编译器居然没有抱怨出错,为何? 函数名和参数列表(参数名称和参数个数)都相同,为何没有违反函数重载规则??
编译器处理后的结果:
注意 const member function 通过 "name mangling"以后多了一个"K".
首先 咱们来看看 const member function 的语义: const member function 不能修改调用该函数的对象
编译器是怎样保证这种特性的呢? 答案是 this 指针
第0部分 咱们讲到 第一个fun 函数能够改写为:
int Widget::fun(Widget* const this, int a); //this 是常指针
第二个fun 函数由于 const 的缘由,改写为:
int Widget::fun(const Widget* const this, int a); // this 是常指针 而且指向 常对象(不可修改)
看到这两个函数改写形式,是否是有点眼熟? 正是! 正是第2部分 函数重载中第2组状况,因此这部分我就此打住
仍是多说一句吧:const member function 有两种不一样的语义(我的以为很恶心),详见 Effective c++ 第3条
4. static function
class Widget { public: int fun1() {...} virtual int fun2() {...} static int fun2() {...} private: int m1; static int m2; };
static 成员函数和成员数据都独立于该类的任意对象而存在.不是类对象的组成部分
static 成员遵循正常的 public/private 访问规则
(1)static member function
static 成员是类的组成部分但不是任何对象的组成部分,所以,static 成员函数没有 this 指针
static 函数没有 this 指针,因此 static 函数不能是 const member function 和 virtual function
(2)static member data
static member data 不属于任何对象,因此不是经过构造函数进行初始化的
static member data 在类定义体外部定义,且在外部定义时进行初始化(static const int a = 0 能够在类内定义. 真心不喜欢这种打补丁式的特性设计)
static double ClassName::StaticData = 0.0;
(3)调用规则
ClassName::StaticFunc(...);
static function 能够直接使用该类的 static data,不能使用该类的 non-static data(由于全部 non-static data 必须经由 this 指针调用)
static function 与 non-static function 之间:
static function 属于类,在该类实例化对象以前已经定义而且分配内存空间,而 static function 必须在类实例化对象以后才定义分配内存空间,故:
static function 调用 non-static function 是错误的
non-static function 调用 static function 是正确的
5. virtual function
class Base { public: virtual void func() const = 0; }; class Derived : public Base { public: virtual void func() const; }; Base* pBase = new Base; Base* pDerived = new Derived;
(1)对象的静态类型:对象在程序中被声明时所采用的类型
pBase声明为 Base*,因此 pBase 静态类型为 Base*(不论真正指向的对象类型)
pDerived声明为 Base*,因此 pDerived 静态类型为 Base*(不论真正指向的对象类型)
(2)对象的动态类型:目前实际所指对象的类型
pBase真正指向的对象类型为 Base*,因此 pBase 动态类型为 Base*
pDerived真正指向的对象类型为 Derived*,因此 pDerived 动态类型为 Derived*
virtual 函数系动态绑定而来,最终调用哪一份函数实现代码,取决于发出调用的那个对象的动态类型
class Shape { public: virtual void draw() const = 0; virtual void error(const std::string& msg); int objectID() const; }; class Rectangle : public Shape {...}; class Elllipse : public Shape {...};
成员函数的接口老是被继承,由于 public 继承意味着 is-a,因此对base class 为真的任何事情必定也对其 derived class 为真
咱们如今将类的 non-static function 分为三类:
a. pure virtual member function b. non-pure virtual member function c. non-virtual member function
(1) pure virtual member function:
pure virtual 函数有两个最突出的特性:
a.必须在全部 Derived class 中重定义该函数
b.它们在 抽象基类里面一般没有定义
声明一个 pure virtual 函数是为了让 derived class 只继承 函数接口
(2) non-pure virtual member function:
non-pure virtual 函数是为了让 derived class 继承 函数接口 和 函数缺省实现
函数为 non-pure virtual 函数,代表 Base class 能够选择是否为该函数提供一个缺省实现,而且让 Derived class 继承该函数缺省实现
a. 若 Base class 没有提供缺省实现,Derived class 也没有重定义该函数,固然会编译错误
b. 若 Base class 没有提供缺省事项,Derived class 重定义了该函数,调用函数的重定义版本
c. 若 Base class 提供了缺省实现,Derived class 没有重定义该函数,则默认继承该函数的缺省实现
d. 若 Base class 提供了缺省实现,Derived class 重定义了该函数,则触发c++多态机制,最终调用的函数取决于发出调用的对象的动态类型
(3) non-virtual member function:
声明 non-virtual 函数是为了让 derived class 继承 函数接口 和 函数强制性实现(注意区别于 函数缺省实现)
实际上 non-virtual 函数表现出 不变性(全部Derived class 都不能重定义该函数,而且都共享 Base class 的同一份强制性函数实现)
6.To be continue
最初学c++时,最初的印象就是一个函数貌似有好多的关键词能够修饰, inline、const、static、virtual...
最大的迷惑就是这些关键词到底谁和谁能够在一块儿,谁和谁不能在一块儿?
慢慢的学习,陆续的理解了这些关键词背后的语义以后,以为也不过如此
在这里我根据本身的理解,结合本文最后奉上一幅图
ps:我没有加入 friend function(这是c++封装与访问机制的妥协物,是一个典型的c++补丁式特性)
写在最后的话:
每次我看到这幅函数图,我都想仰天长叹,喃喃自语: c++啊,想说爱你不容易...
欢迎你们批评指正,共同窗习...
转载请注明出处,原文地址:http://www.cnblogs.com/wwwjieo0/p/3452930.html