C++类成员函数
c++的两大特色是多态和模板。其中多态是通过继承和虚函数来实现的,其中虚函数是通过每个对象里面的虚表来实现的。如果这个对象的类有虚函数,那么这个类就有一张虚表,存的是每个虚函数的入口地址,而这个类的每个对象,都会有一个4字节的指针,指向这张虚表,这个就是虚指针。
上面一段话很多人都知道,但是如果问普通成员函数,编译器是怎么找到它的入口地址的呢?也就是说,怎么进行调用?为什么A类一个foo函数和B类一个foo函数,A类的对象.foo就一定是调用A的foo?有人会说运行时类型识别RTTI。假如识别出A类的对象,那么A类的对象a,和A类的对象b,分别调用foo处理成员变量的时候,为什么不会a调用foo处理的是b的数据?这个问题只要问深几层,RTTI的说法就不攻自破。其实面向对象语言都是用反射,也就是对象把自己的地址作为参数,传入成员函数的this指针,通过this指针来确定数据的。
还记得函数重载的实现机制吧,在c++里面是通过将函数名和函数的参数列表结合来区分不同的函数,同名但不同参数列表的函数,是不同的函数。例如void foo(int,int)在c里面编译器是用_foo来识别,在c++里面是用_foo_int_int来识别。
那么A类的foo函数和B类的foo函数,如果参数列表一致,怎么区分它们呢?用A类的一个对象调用成员函数,这个成员函数用到成员变量,怎么知道是在用这个对象的数据呢?这个解释,就是this指针。A类的foo(int,int)函数,已经被编译器编译成_foo_A*_int_int,也就是说隐含已经加入了函数所属类的信息,调用A类对象a.foo的时候,已经在调用foo(const A * this,int,int)这个函数。这也解释了,为什么在成员函数内部,使用成员变量,有时候(并不是全部情况下都如此)前面加this和不加this没有区别,编译器已经把foo里面用到的成员变量,用this指针指向了。
如果你在全局中,这样定义这样一个函数foo(const A * this,int,int)去模仿成员函数的调用,将不能编译,因为this是关键字,this指针已经成为了c++的机制,在类成员函数用this才做形参(foo(int this)或者foo(float this)),也是非法的。这样试图和编译器生成的函数的第一个参数this来一个重定义。
举个例子,下面一段代码:
class A
{
public:
void foo(){ cout << "A foo" << endl; }
};
class B
{
public:
void foo(){ cout << "B foo" << endl; }
};
oo将编译成:
void foo(const A* this)( cout << "A foo" << endl; }
void foo(const B* this)( cout << "B foo" << endl; }
调用a.foo(),编译器将转换成foo(&a)
有趣的是,A* pa = NULL; pa->foo();也没有异常退出,因为没有通过this引用任何成员变量,这个时候不过this指针为NULL而已。
静态成员函数
上面说的只是面向对象的非静态成员函数,如果说到类里面的静态成员函数,解释又是另外一个,请看下文。
1、静态数据成员
特点:
A、内存分配:在程序的全局数据区分配。
B、初始化和定义:
a、静态数据成员定义时要分配空间,所以不能在类声明中定义。
b、为了避免在多个使用该类的源文件中,对其重复定义,所在,不能在类的头文件中
定义。
c、静态数据成员因为程序一开始运行就必需存在,所以其初始化的最佳位置在类的内部实现。
C、特点
a、对相于 public,protected,private 关键字的影响它和普通数据成员一样,
b、因为其空间在全局数据区分配,属于所有本类的对象共享,所以,它不属于特定的类对象,在没产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它。
D、访问形式
a、 类对象名.静态数据成员名
b、 类类型名:: 静态数据成员名
E、静态数据成员,主要用在类的所有实例都拥有的属性上。比如,对于一个存款类,帐号相对 于每个实例都是不同的,但每个实例的利息是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局区的内存,所以节省存贮空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了,因为它们实际上是共用一个东西。
2、静态成员函数
特点:
A、静态成员函数与类相联系,不与类的对象相联系。
B、静态成员函数不能访问非静态数据成员。原因很简单,非静态数据成员属于特定的类实例。
作用:
主要用于对静态数据成员的操作。
调用形式:
A、类对象名.静态成员函数名()
B、类类型名:: 静态成员函数名()
上面是一段精辟的分析,但是他没有说道编译器是如何实现这个调用的。下面一段代码,将解释这个过程:
#include <iostream>
using namespace std; class A
{
public:
static int count;
void foo(){ cout << "A foo" << endl; } // 如果这样声明和定义一个成员函数,将直接产生一个 foo(A& this) 类型的函数
static void goo(){ cout <</* this << */"A goo"<< endl; } // 静态函数没有 this 指针
void too(){ cout << typeid(*this).name() << endl; }
}; int A::count = ; class B : public A
{
public:
// 如果静态函数只能通过域运算符来调用的话,那class在静态意义下就成了命名域的概念了
// 如果没有下面这个函数,A类的goo函数将会继承下来,说明作为类的命名空间,也可以继承
static void goo(){ cout << "B goo" << endl;} // 一个问题,静态的成员函数,是怎么区分开的呢?
}; int main()
{
A a , *pa;
pa->goo(); // 静态也跟普通函数一样,没有多态效果
a.goo(); // 是否是直接翻译成 A::goo() 竟然说我没引用过 a !- -
// a.foo(); // this 是关键字,不能拿来作为一个全局函数的参数,在转成 foo(&a) 的时候,一定是调用 foo(A& this) 这个函数
// A::foo(); // 这个 foo 没有带参数,只能调用静态的,静态的就直接编译成类似全局函数的不带 this 参数的类型
A::goo(); // 这样调用是正确的,这说明它没有 this 指针作为形参
system("pause");
return ;
}
编译运行,将产生一个警告,说对象a和指针pa从来没引用过!通过对象调用静态函数,已经通过类型识别,被编译器替换成A::goo(),这个是由编译器做的,所以替换之后a就只定义了,但是没用引用过。换句话,static的成员函数,只能通过域运算符来调用,无论你是用对象调用还是用指针调用。
static的成员变量,也是如此,只能通过翻译成域运算符来调用。这样两者结合在一起,说明了“类其实除了可以定义变量,还有一个重要的作用就是它是个命名域,相当于std::cout这样。而且,这个命名域,还能继承下来。”
还记得stl里面的迭代器吗?迭代器的类,就是为了不污染全局空间,把迭代器类声明在某个stl容器类里面。这个类里面的类,只能通过容器和域运算符才可见,有趣的是,这个类中类,也可以继承。
#include <iostream>
using namespace std; class A
{
public:
class C{}; // 类中类
}; class B : public A
{
}; int main()
{
B::C c; // 除了父类,子类也可见
return ;
}
这里由于某个问题找到了这边博文,感觉不错,转自:http://blog.csdn.net/yuanyirui/article/details/4594805
C++类成员函数的更多相关文章
- 重载运算符:类成员函数or友元函数
类成员函数: bool operator ==(const point &a)const { return x==a.x; } 友元函数: friend bool operator ==(co ...
- C++类成员函数的重载、覆盖和隐藏区别?
C++类成员函数的重载.覆盖和隐藏区别? a.成员函数被重载的特征:(1)相同的范围(在同一个类中):(2)函数名字相同:(3)参数不同:(4)virtual 关键字可有可无.b.覆盖是指派生类函数覆 ...
- C++学习46 getline()函数读入一行字符 一些与输入有关的istream类成员函数
getline函数的作用是从输入流中读取一行字符,其用法与带3个参数的get函数类似.即 cin.getline(字符数组(或字符指针), 字符个数n, 终止标志字符) [例13.7] 用get ...
- C++的类成员和类成员函数指针
类成员函数指针: 用于访问类成员函数,和一般函数指针有区别. 类成员函数处理的是类数据成员,声明类成员函数指针的同时,还要指出具体是哪个类的函数指针才可以.调用时也要通过对象调用. 而对于类的静态成员 ...
- python 类成员函数
http://cowboy.1988.blog.163.com/blog/static/75105798201091141521583/ 这篇文章总结的非常好 主要注意的地方是 1,在类内调用成员函数 ...
- ### C++总结-[类成员函数]
C++类中的常见函数. #@author: gr #@date: 2015-07-23 #@email: forgerui@gmail.com 一.constructor, copy construc ...
- C++:类成员函数的重载、覆盖和隐藏区别?
#include <iostream> class A { public: void func() { std::cout << "Hello" <& ...
- 理解ATL中的一些汇编代码(通过Thunk技术来调用类成员函数)
我们知道ATL(活动模板库)是一套很小巧高效的COM开发库,它本身的核心文件其实没几个,COM相关的(主要是atlbase.h, atlcom.h),另外还有一个窗口相关的(atlwin.h), 所以 ...
- 并发编程: c++11 thread(Func, Args...)利用类成员函数创建线程
c++11是VS2012后支持的新标准,为并发编程提供了方便的std::thread. 使用示例: #include <thread> void thread_func(int arg1, ...
- 直接调用类成员函数地址(用汇编取类成员函数的地址,各VS版本还有所不同)
在C++中,成员函数的指针是个比较特殊的东西.对普通的函数指针来说,可以视为一个地址,在需要的时候可以任意转换并直接调用.但对成员函数来说,常规类型转换是通不过编译的,调用的时候也必须采用特殊的语法. ...
随机推荐
- Naive Bayes Classifier 朴素贝叶斯分类器
贝叶斯分类器的分类 根据实际处理的数据类型, 可以分为离散型贝叶斯分类器和连续型贝叶斯分类器, 这两种类型的分类器, 使用的计算方式是不一样的. 贝叶斯公式 首先看一下贝叶斯公式 $ P\left ( ...
- Ubuntu16.04下的英文词典Artha
地址: http://artha.sourceforge.net http://artha.sourceforge.net/wiki/index.php/Download 在Ubuntu下可以直接安 ...
- 读书笔记--<精益和敏捷开发大型项目应用指南>
[摘要] 3月份的时候,根据教练和其他多为项目经理的推荐,开始阅读这本书:本书共三大部分.12个章节,第一部分:思考工具,第二部分:组织工具:第三部分:杂记:全书相当于对精益思想和敏捷团队组织.Scr ...
- 有关windows Gateway Ipsec 和NAT 兼容性问题
1.简单通信拓扑: 将Windows 平台 作为一个网关,同一时候开启IPsec 和NAT来支持private和public的通信. 注意:IPSEC Gateway 和 Client1 Ipsec ...
- 用命令行发邮件——让你更加了解smtp
本文演示用命令行发送邮件的过程. SMTP 首先介绍下smtp协议--简单邮件传输协议 (Simple Mail Transfer Protocol, SMTP) 是事实上的在Internet传输em ...
- ubuntu下安装迅雷thunder
迅雷是windows xp下必装的下载工具,作为一款跨协议的下载软件,迅雷的下载速度极其强悍. 那么在ubuntu下能否安装迅雷呢? 到ubuntu中文论坛逛了一圈,发现有现成的wine-thunde ...
- easyui的日期控件
1.日期控件只能点击控件进行选择, 不可手动编辑input框中的日期内容 editable="false" 2.日期控件既不可点击, 也不可手动编辑input框中的日期内容 dis ...
- 在 iOS 中实现方法链调用
编译:伯乐在线 - 林欣达 如有好文章投稿,请点击 → 这里了解详情 如需转载,发送「转载」二字查看说明 前言 链式调用(chained calls)是指在函数调用返回了一个对象的时候,使得这个调用链 ...
- 下载历史版本App
文/timhbw(简书作者)原文链接:http://www.jianshu.com/p/edfed1b1822c著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”. 1.软件准备 [必备]C ...
- 关于新加坡IT薪酬和找工作网站
关于新加坡IT薪酬 很多朋友发邮件或留言问我关于新加坡IT薪酬的问题,由于前段时间比较忙,所以没有及时一一回复,在此表示抱歉. 新加坡IT薪酬范围大概如下(月薪,新加坡币对人民币为1:5): Juni ...